■
3 クラス設計
3.1 クラス単体で正常に動作するように設計する
良いクラスの構成要素は下記。
3.2 成熟したクラスへ成長させる設計術
- コンストラクタで確実に正常値を設定する。
- 計算ロジックをデータ保持側に寄せる。
- インスタンス変数/メソッド引数/ローカル変数を不変にする。
- 変更したい場合は新しいインスタンスを作成する。
- 値の渡し間違いを型で防止する。それに伴い、プリミティブ側ではなく独自の型を利用する。
- 「金額同士の掛け算」などの現実の営みにはないメソッドを追加しない
class Money # 3. インスタンス変数/メソッド引数/ローカル変数を不変にする。 # ※ Rubyだと不変にはできないが、可能な努力は行う。 attr_reader :amount, :currency def initialize(amount, currency) # 1. コンストラクタで確実に正常値を設定する。 raise ArgumentError.new("金額が0以上でありません") if amount < 0 raise ArgumentError.new("通貨を指定してください") if currency.nil? || currency.empty? @amount = amount @currency = currency end # 2. 計算ロジックをデータ保持側に寄せる。 def add(other) # 5. 値の渡し間違いを型で防止する。 raise TypeError.new("引数がお金ではありません") unless other.instance_of?(Money) raise ArgumentError.new("通貨単位が異なります") unless currency == other.currency added_amount = amount + other.amount # 4. 変更したい場合は新しいインスタンスを作成する。 Money.new(added_amount, currency) end end savings = Money.new(10000, "円") allowance = Money.new( 1000, "円") new_savings = savings.add(allowance) puts new_savings # => #<Money:0x00007f9733823468> puts new_savings.amount # => 11000
4 不変を活用する -安定動作を構築する-
4.1 再代入
- 1.再代入/破壊的代入は行わない。
# 1.再代入/破壊的代入は行わない。 def damage() basic_attack_power = ( member.power + member.weapon_attack ).floor final_attack_power = ( basic_attack_power * ( 1.0 + member.speed / 100.0 ) ).floor reduction = ( enemy.defence / 2 ).floor damage = [0, final_attack_power - reduction].max damage end
4.2 可変がもたらす意図せぬ影響
副作用について。メソッドには主作用と副作用が存在する。
副作用のある関数を作らないために、関数が下記の項目を満たすことを前提に設計する。
- a.データ(=状態)を引数で受け取る
- b.状態を変更しない
- c.値は関数の戻り値として返す
# 1.可変インスタンスを使いまわさない。 attack_power_a = AttackPower.new(20) attack_power_b = AttackPower.new(20) weapon_a = Weapon.new(attack_power_a) weapon_b = Weapon.new(attack_power_b) weapon_a.attack_power_a.value += 5
# 2.クラスに副作用を与えるメソッドは保守が大変になることを理解し、避ける。 class AttackPower attr_accessor :value MIN = 0.freeze private_constant :MIN def initialize(value) raise ArgumentError if value < MIN @value = value end # a.データ(=状態)を引数で受け取る def reinforce(increment) raise TypeError unless increment.instance_of?(AttackPower) # b.状態を変更しない # c.値は関数の戻り値として返す AttackPower.new(value + increment.value) end def disable AttackPower.new(MIN) end end attack_power = AttackPower.new(20) reinforced_attack_power = attack_power.reinforce(AttackPower.new(15)) disabled_attack_power = attack_power.disable puts "reinforced attack power : #{reinforced_attack_power.value}" # => reinforced attack power : 35 puts "disabled attack power : #{ disabled_attack_power.value}" # => disabled attack power : 0
4.3 不変と可変の取り扱い方針
- デフォルトは不変にする。
- パフォーマンスに影響を与える場合は可変にしてもOK。
- 局所的にしか利用しないことが明らかなローカル変数は可変にしてもOK。
- 可変にする場合は正しく状態変更されるようにする。
- ex.「本来負数になってはいけないのに、負数になる」といったことは避ける。
- コード外のやりとり(ex.ファイルやDBの読み書き)は局所化する。
低凝集 -バラバラになったモノたち-
5.1 staticメソッド(≒クラスメソッド)の誤用
※補足 Rubyにはstaticメソッドが無く、似た性質のクラスメソッドが存在する。 下記の記事でjavaのstaticメソッドとRubyのクラスメソッドを比較している。 https://qiita.com/1plus4/items/b37ec6ea90569ffdebfe
- クラスメソッドの誤った使い方をすると、データとロジックが分離してしまう。
- インスタンスメソッドのふりをしたクラスメソッドも同様にデータとロジックが分離してしまうため気を付ける。
- 「そのクラスメソッドをインスタンスメソッドに変更しても問題ないか?」を考えてみる。
- 凝集度に無関係なものはクラスメソッドとして設計する。
- ex. ログ出力用メソッド, フォーマット変換用メソッド
5.2 初期化ロジックの分散
- 初期化ロジックが分散することを避けるために、privateコンストラクタとファクトリメソッドで目的別初期化を行う。
- ファクトリメソッドが増えすぎたら、ファクトリクラスを検討する。
class GiftPoint attr_reader :value MIN_POINT = 0.freeze STANDARD_MEMBERSHIP_POINT = 3000.freeze PREMIUM_MEMBERSHIP_POINT = 10000.freeze private_constant :MIN_POINT, :STANDARD_MEMBERSHIP_POINT, :PREMIUM_MEMBERSHIP_POINT private_class_method :new def initialize(point) raise TypeError.new unless point.instance_of?(Integer) raise ArgumentError.new unless MIN_POINT < point @value = point end class << self def for_standard_membership send(:new, STANDARD_MEMBERSHIP_POINT) end def for_premium_membership send(:new, PREMIUM_MEMBERSHIP_POINT) end end end standard_membership_point = GiftPoint.for_standard_membership puts standard_membership_point.value # => 3000 premium_membership_point = GiftPoint.for_premium_membership puts premium_membership_point.value # => 10000 other_membership_point = GiftPoint.new(1000) puts other_membership_point.value # => private method `new' called for GiftPoint:Class (NoMethodError)
5.3 共通処理クラス(Common・Util)
問題点
[問題点]
- 共通処理クラスの共通処理はstaticメソッドとして実装されがち。 それにより、低凝集構造を生み出してしまう。
- CommonやUtilといった名前から、共通処理クラスには様々なロジックが雑多に置かれがち。
[解決策]
- 安易に共通処理クラスを作らずに、オブジェクト指向設計の基本に基づいて設計する。
class AmmountIncludingTax attr_reader :value def initialize(ammount_excluding_tax, tax_rate) raise TypeError.new unless ammount_excluding_tax.instance_of?(Integer) raise TypeError.new unless tax_rate.instance_of?(Float) raise ArgumentError.new if ammount_excluding_tax < 0 raise ArgumentError.new if tax_rate < 0 @value = ammount_excluding_tax * (1 + tax_rate) end end
横断的関心事
下記のような、ユースケースに広く横断する事柄を、横断的関心事と呼ぶ。
- ログ出力
- エラー検出
- デバッグ
- 例外処理
- キャッシュ
- 同期処理
- 分散処理
横断的関心ごとに関する処理は共通処理としてまとめてOK。
begin shopping_cart.add(product) rescue Logger.report("例外が発生しました") end
5.4 出力引数を使わない
[問題点]
- 引数が入力なのか出力なのかを、ロジックを読んで確認しなければいけない。
[解決策]
- データとデータを操作するロジックを同じクラスに凝集する。
class Location attr_reader :x, :y def initialize(x, y) @x = x @y = y end def shift(shift_x, shift_y) new_x = x + shift_x new_y = y + shift_y return Location.new(new_x, new_y) end end location = Location.new(1, 2) new_location = location.shift(2, 3) puts "Old location: #{location.x}, #{location.y}" # => Old location: 1, 2 puts "New location: #{new_location.x}, #{new_location.y}" # => New location: 3, 5
5.5 多すぎる引数
- 引数が多くなりすぎた場合には、意味ある単位ごとにクラス化し、引数ではなくインスタン変数として表現する。
5.6 メソッドチェイン
- 他のオブジェクトの内部状態を尋ねたり、その状態に応じて呼び出しが側が判断するのは、あらゆる箇所からのアクセスを引き起こすためNG。
- 呼び出し側はただメソッドで命ずるだけで、命令された側で適切に判断や制御を行うように設計するべき。
class Equipments attr_accessor :can_change, :head, :armor, :arm def initialize(can_change, head, armor, arm) raise TypeError unless can_change.is_a?(TrueClass) || can_change.is_a?(FalseClass) raise TypeError unless head.is_a?(Equipment) raise TypeError unless armor.is_a?(Equipment) raise TypeError unless arm.is_a?(Equipment) @can_change = can_change @head = head @armor = armor @arm = arm end def equip_armor(new_armor) raise TypeError unless new_armor.is_a?(Equipment) armor = new_armor if @can_change end def deactivate_all head = Equipment::EMPTY armor = Equipment::EMPTY arm = Equipment::EMPTY end end
6 条件分岐
6.1 条件分岐のネストによる可読性低下
[問題点]
下記はロジックの見通しが悪くなりがち。
- 条件分岐のネスト
- else句
[解決策]
- 早期returnを利用する。
- また、条件ロジックと実行ロジックの分離ができるという利点もある。
6.2 switch文の重複
[問題点]
- 同じ条件式のswitch文が複数書かれていく。
- そうすると、仕様変更時の修正漏れが発生する。
[解決策]
- 条件分岐を一箇所にまとめる。
class Magic def initialize(magic_type, member) raise TypeError unless magic_type.is_a?(MagicType) raise TypeError unless member.is_a?(Member) case magic_type when "fire" @name = "ファイア" @cost_magic_point = 2 @attack_power = 20 + (member.level * 0.5).to_i @cost_technical_point = 0 when shiden @name = "紫電" @cost_magic_point = 3 @attack_power = 30 + (member.level * 0.5).to_i @cost_technical_point = 0 when "hell_fire" @name = "ヘルファイア" @cost_magic_point = 5 @attack_power = 50 + (member.level * 0.5).to_i @cost_technical_point = 0 else raise AugumentError end end end
- interfaceクラスを利用する。 ※Rubyにはinterfaceがないので、interfaceを意識した実装を行う。
class Fire attr_reader :member def initialize(member) @member = member end def name "ファイア" end def cost_magic_point return MagicPoint.new(20) end def attack_power return AttackPower.new(50) end def cost_technical_point return TechnicalPoint.new(0) end end class Shiden attr_reader :member def initialize(member) @member = member end def name "紫電" end def cost_magic_point return MagicPoint.new(10) end def attack_power return AttackPower.new(30) end def cost_technical_point return TechnicalPoint.new(10) end end magics = {} magics[:fire] = Fire.new(member) magics[:shiden] = Shiden.new(member) def magic_attack(magic_type) using_magic = magics[magic_type] # 処理 end
DXとIT化の違いを整理
結論
DXの意味
分類 | 意味 |
---|---|
誤 | デジタルへと変化していくこと transform into デジタル |
正 | デジタルを用いてビジネスを変化させていくこと transform ビジネス using デジタル |
DXとIT化の違い
取り組み | ポイント |
---|---|
DX | ・市場の変化への対応としての施策。 ・対顧客の施策。 ・ビジネス面での変化が発生する。 ・ビジネスにおける価値を作り出す。 ・過程としてIT化が必要。 |
IT化 | ・あくまで社内に閉じた施策。 ・業務効率化。 |
整理の記録
段取り
- 「DXとは何か?」を調べる。
- 「IT化とは何か?」を調べる。
- 「DXとIT化の違いについて、どのように論じられているか?」を調べる。
- DXとIT化のそれぞれのポイントを整理する。
DXとは何か?
デジタルトランスフォーメーション(英: digital transformation)は、デジタルテクノロジーを使用して、ビジネスプロセス・文化・顧客体験を新たに創造(あるいは既存のそれを改良)して、変わり続けるビジネスや市場の要求を満たすプロセスである。デジタル変革やDXともいう。
IT化とは何か?
情報技術(じょうほうぎじゅつ、英: information technology、IT)とは、コンピュータを使ってあらゆる種類の電子的なデータや情報を作成、処理、保存、取得、交換することである。
DXとIT化の違いについて、どのように論じられているか?
Googleで「DX IT化 違い」で調べた際のTOP5の記事を見てみる。
No | URL | 要旨 |
---|---|---|
1 | j-net21.smrj.go.jp | IT化は視点が「社内」で、DXは視点が「顧客や社会」 |
2 | www.intec.co.jp | IT化の後段階として、顧客エンゲージメントを高めたり、顧客体験価値の向上を見据えるのがDX |
3 | www.brainpad.co.jp | IT化による変化は「量的変化」、DXによる変化は「質的変化」 |
4 | sms.supership.jp | DXとはデジタル技術や製品、サービスなどに変革を起こすことで、IT化はDXの手段 |
5 | www.ntt.com | IT化はデジタル化による業務効率化。DXは業務をデジタル化し課題を解決するだけでなく、ビジネスモデルを変える取り組み。 |
DXとIT化のそれぞれのポイント
(👆で結論として記載)