RubyTapas #194 - #198
194 String Format
★
"Average high: %.1f, low: %.1f" % [avg_hi, avg_lo] # => "Average high: 49.7, low: 25.4"
format, sprintfも同じ
- %.1f
- %d
- %x -> 17 (hex)
- %#x -> 0x17
- %#X -> 0X17
- %o
- %#o -> 027
- %b
- %#b -> 0B10111
- %e -> 2.400000e+19
- %g
- 123.0 -> 123
- 2.4e19 -> 2.4e+19
- %p (inspect)
- %% -> escape '%'
195 Advanced String Formats
★★
temperature listing code
padding with space
puts "x: % d, y: % d\nx: % d, y: % d" % [123, 456, -789, 321]
長さを渡す、hashを渡す
width = -4 DAYS.each_with_index do |day, i| puts "%-9s High: %#{width}d Low: %#{width}d" % [day, C(HIGHS[i]), C(LOWS[i])] DAYS.each_with_index do |day, i| values = {day: day, high: C(HIGHS[i]), low: C(LOWS[i])} puts "%<day>-9s High: %<high>3d Low: %<low>3d" % values printf "my %{vehicle} is full of %{animal}", vehicle: "hovercraft", animal: "eels"
使い方と存在を最近は忘れてた
196 String Templates
★★
evalするから良くない
class MyLogger attr_accessor :formatter def initialize @formatter = ->(data) { "#{data[:severity]} #{data[:time]} #{data[:host]} #{data[:pid]} #{data[:message]}" } end def error(message) data = { message: message, time: Time.now, host: Socket.gethostname, pid: $$, severity: "ERROR" } puts formatter.call(data) end end logger = MyLogger.new logger.formatter = ->(data) { "#{data[:severity][0]} #{data[:message]}" } logger.error "Bogosity increasing!" # >> E Bogosity increasing!
format使う セキュリティなといろいろな面で良い、分かり易い
class MyLogger attr_accessor :format def initialize @format = '%<severity>s %<time>s %<host>s %<pid>s %<message>s' end def error(message) data = { message: message, time: Time.now, host: Socket.gethostname, pid: $$, severity: "ERROR" } printf format, data end end logger = MyLogger.new logger.format = "%<severity>.1s %{message}" logger.error "Klingons off the starboard bow!" # >> E Klingons off the starboard bow!
197 Decorator
★
object should have a single responsibility
require 'delegate' class AuditedAccount < SimpleDelegator def initialize(account, audit_log) super(account) @audit_log = audit_log end def deposit(amount) super @audit_log.record(number, :deposit, amount, balance_cents) end def withdraw(amount) super @audit_log.record(number, :withdraw, amount, balance_cents) end end
require "./bank_account" require "./audited_account" require "./audit_log" account = BankAccount.new(123) account = AuditedAccount.new(account, AuditLog.new) account.deposit(1000) account.withdraw(500) # >> 2014-03-25 19:55:48 -0400 #123 deposit 10.00 (10.00) # >> 2014-03-25 19:55:48 -0400 #123 withdraw 5.00 (5.00)
Audited bank accounts are responsible for adding audit logging to a bank account, without worrying about how to maintain a balance.
それぞれのクラスがsingle responsibilityを守る
198 Decorator Transparency
★★
returnがnil
def withdraw(amount) super @audit_log.record(number, :withdraw, amount, balance_cents) end
値を返す
def withdraw(amount) new_balance = super @audit_log.record(number, :withdraw, amount, balance_cents) new_balance end def withdraw(amount) super.tap do @audit_log.record(number, :withdraw, amount, balance_cents) new_balance end end
これでも最後に一行追加するだけで壊れてしまう
最初のnotifyの部分を修正した方が良い
def withdraw_and_notify(customer, account, amount) new_balance = account.withdraw(amount) customer.notify("Your new balance is %.2f" [new_balance / 100.0]) end ↓ def withdraw_and_notify(customer, account, amount) account.withdraw(amount) new_balance = account.balance_cents customer.notify("Your new balance is %.2f" [new_balance / 100.0]) end
そもそもwithdrawの返り値を使わないようにする withdrawして、その後金額を読み込む