はじめに
インフキュリオンでプログラマーとマネージャーをしている浅田です。(投稿はこれが2回目) 最近はWallet Stationの性能改善と技術負債の対応をひたすらやっています。
APIの大規模リファクタリング、クエリチューニング、keycloakで認証認可サーバ構築など、インフキュリオンではいろいろやらせてもらってます。
最近、新卒社員向けに設計の話をする機会があり、私が約10年間プログラマーをやってきた中で、設計において大事にしていることをまとめていきました。
今回はその中でも特に重要だと考えている命名について 以下のような内容を記事にまとめました。
またこの記事は、「現場で役立つシステム設計の原則」の第1章「小さくまとめてわかりやすくする」を参考にしており、自分の経験とすり合わせながら内容をまとめています。
設計の目的
システム開発が、 もし納期を守って納品して終わりであれば、 性能要求や機能要求を満たせて動きさえすればOKで、 設計を重要視しないという考え方もあるかもしれません。 しかし、 最近のほとんどのシステム開発においては、 (使い物にならず即捨てられるケースを除けば) リリースしたらそこからは保守開発をほぼ必ず行い続けることになります。 設計は作ってリリースして終わりでなく、 そこからさらに保守開発し続ける際に効果を発揮してくると私は思います。 私が運営している個人ブログにこの辺りは詳しく、 まとめているので興味がある方はご覧ください。 poppingcarp.com もし設計をテキトーにやってソースコードが整理整頓されておらず、 システムリリースをしてしまうと、 機能追加したり仕様変更といった保守開発の作業がカオスになります。 リリース時はバグっていなくても、 その先の保守開発でバグる可能性が非常に高くなったり、 保守開発の際に非常に危険でめちゃくちゃ時間がかかったり・・・ そうならないように、きちんとソースコードを整理整頓して、 どこに何があるかわかりやすくする必要があり、 ソースコードを整理整頓するために、 設計のいろんなテクニックがあるのだと私は思います。
設計において命名と小さく分けることの関係と重要性
ここまで設計が目指すところについて見てきたので、 ここからは設計において最も基本で重要となる(個人的にそう思う) 命名と関心事を分けることの重要性について見ていきます。 命名が微妙だと後から見た人が混乱したり、 略語でamt(金額を表してるつもり)より amountの方がわかりやすい。 と、ここまでは言うまでもなく当たり前だと思いますが、、、、 もしクラス名にふわっとした命名をすると、 そのクラスにふわっといろんな関心事が集まってくる。 その結果、いろんな関心事が歪に1箇所に集まることで、 分岐やフラグ、パラメータやモードなどで処理がカオスになってくる。その結果、変更の影響範囲が危険で厄介になる。といったことに陥ります。
(現場でリファクタリングや設計をするとこういうケース本当によく見かけます)
具体例:変数名
例えば、変数名がふわっとしてしまうと 最悪、色んな意味で1つの変数が使い回されてしまったりします。 (そもそもimmutableにしとけよってのは一旦置いておいて・・・)
【Bad】amountを使い回した例
var amount = 1000.0 val fee = 50 val tax = 0.1 amount *= (1 + tax) amount += fee * (1 + tax)
【Good】目的に特化した変数を用意した例
val amount = 1000.0 val feeIncludeTax = fee * (1 + tax) val amountIncludeTax = amount * (1 + tax) val billingAmount = amountIncludeTax + feeIncludeTax
具体例:クラス名
例えば、クラス名がふわっとしてしまうと 全然関係のない関心事のメソッドがそこに集りがちです。
【Bad】商品クラスにいろんな関心事が集まってしまった例
data class Goods( ・ ・ ・ ){ fun shippingLogic(){・・・} fun orderLogic(){・・・} }
【Good】出荷商品クラスには出荷に関する関心事のみの例
data class ShippingGoods( ・ ・ ・ ){ fun orderLogic(){・・・} }
具体例:メソッド名
例えば、メソッド名がふわっとしてしまうと 一つのメソッドの中でいろんな処理をする神メソッドになりがちです。
【Bad】modeでいろんな取引を分岐させる例
fun transaction(mode: String) { when(mode) { "1" -> // オペレーターの取引 "2" -> // エンドユーザの取引 "3" -> // 店舗担当者の取引 else -> // エラー } }
【Good】オペレーターが行う取引に特化した例
fun operatorTransaction() { // オペレーターに特化した取引の処理を書く }
などなど・・・ 「命名がふわっとした」モノが出来上がると、 歪にいろんな関心事が絡まって1箇所に集まるので、 仕様変更や機能追加の際に 影響範囲がとてつもなく広かったり、 プログラマーが混乱しやすく、バグが発生しやすくなり、 変更時の影響調査も大変になります。 こうならないように、 とある小さな目的に特化してクラスやメソッドを命名するのが重要です。 目的から外れるもの(命名と関係ない目的のもの)は 別のクラスやメソッドして切り出し、 どこに何が書いてあるかを整理して保守しやすく、 影響範囲が歪に波及しないように意識し続けることも重要です。
そうすることで、 強く関連するものが1箇所に集まり、 どこに何があるかの整理整頓もしやすくなります。 命名が設計(どこに何が書いてあるかの整理)の基本であり、 いろんな勉強会で登場するくらい重要 であることがここまで見ると駆け出しエンジニアの方達にも 納得できるかなと思います。
おまけ
ここからはおまけで、 現場でのあるあるなのですが・・・
リリース当初は綺麗に命名できて関心事を小さく分けて整理できていても、 保守開発で仕様変更や機能追加を重ねるごとに当時のクラスやメソッドに 徐々にいろんな意味を持たせてしまって結果的に命名がぼやける。 ということもよくあります。 (自分の経験ではこっちの方が陥りやすい印象) 目の前の仕様変更や機能追加の対応を、 リリース当時の命名の意図を無視して (人の入れ替えも激しく引き継ぎもろくにしなくて)、 とりあえず処理をコピーしたり分岐ちゃちゃっと増やして その場しのぎの改修を繰り返してる現場では良くこれになります。 その結果、 リリース当初は小さく目的に特化した命名やメソッド、クラスだったものが、 いつの間にかいろんなフラグや分岐が入り混じってきて パラメータによっていろんなモードで動く巨大メソッド みたいなものが完成してしまいます。 こうならないように命名には妥協しないようにしたいですね・・・
まとめ
- 1つの小さな目的に特化した命名をクラスやメソッドにすることで、関心事を小さくメソッドやクラスに分けることに繋がる。
- その結果、変更時の影響の波及を小さい範囲に抑えることに繋がる。
だから命名をふわっとやっちゃダメ!! 今回のテーマ以外にも、 現場で役立つシステム設計の原則を読んでみて、 今後現場で取り入れたいこと、気付きなどなど まとめ記事を私の個人ブログで作っています。
今でも自分自身現場で壁にぶち当たって基本に立ち返る必要がある際には、 大事にしてる考え方などをまとめているので、 興味ある方は是非ご覧ください!!