Good Code, Bad Code
~持続可能な開発のためのソフトウェアエンジニア的思考~
1.コードに対するソフトウェアエンジニアとしての考えを形作るための理論的な上位の概念
2. 特定の考え方やテクニック
3. 効果的でメンテナンスできるユニットテストを作るための原則と実践
Part1理論編
- コードの品質が大事な理由
- 高品質のコードを達成すべき4つのゴール
- 高品質のコードであることを担保するための6つの柱
- 高品質のコードを書くことで中長期的に時間と労力を節約する方法
コードの品質が大事な理由
コードの品質が〇〇の品質に影響するから
コードの品質がソフトウェアの品質に影響するから
高品質なコード | 低品質なコード | |
---|---|---|
満たすべき要件 | 十分に満たす | イレギュラーなケースで十分に満たされない |
要件の変更 | 少しの追加作業が必要 | 多くの再作業とリファクタリングが必要 |
エラーの発生 | 適切に復旧するか落ちる | データを破壊する可能性がある |
予期せぬシナリオ | 予期しないにも関わらず動作する | データを破壊される可能性がある |
システムへの攻撃 | セキュリティ上の危険もなく、安全 | 謎の状態に陥る、不正アクセスされる |
信頼度が高く、メンテナンスしやすい | 信頼度が低く、メンテナンスしにくい |
コードの品質のゴールは、車を買うのと似ている
車を買うときに何を第一に重視しますか?
おそらく、品質。
- 安全
- ちゃんと動く
- 壊れない
- 想定通りの動きをする(ブレーキが効いたら速度が落ちる)
実現したいことに役にたつコードは高品質
実現したいことを邪魔するコードは低品質
改めて、4つのゴールとは何か
- 正しく動くこと
- 正しく動作し続けること
- 要件の変更に対応しやすいこと
- 車輪の再発明をしないこと
要件の変更に対応しやすいとは
-
100%予測し、すべての潜在的な変更を網羅することではない
-
目の前の要件のみを反映し、将来予測される変更に対応する努力をしないことではない
BadなシナリオA
私たちは、将来どのように要件が変化していくかを100%予測し、すべての潜在的な変更を網羅するようにコードを設計する。
まず、コードやソフトウェアに変更を加える可能性があるすべてのことを調べ上げるのに、数日から数週間費やす。さらに、調べ上げたコードを書く際に、その要件をすべて網羅しているかどうかを細部まで慎重に検討する必要がある。これにより私たちの開発速度はかなり落ち、3ヶ月の開発を予定していたソフトウェアでも1年かそれ以上かかってしまう。ついには競合他社が私たちよりも数ヶ月早く市場に登場したことで、これまでの開発はすべて無駄になり、私たちの可能性の予測はおそらく間違っていることに気づく。
BadなシナリオB
私たちは、要件が変化してく事実を完全に無視する。ただ、目の前の要件を反映したコードを書き、どのコードも変更へ対応しやすくするための努力は一切しない。簡単に破綻するようなコードが至る所に焼き込まれ、小さな問題の解決策はすべて、分離不可能な大きなコードの塊の中に埋め込まれる。3ヶ月後、私たちはソフトウェアの最初のバージョンをリリースする。しかし、最初のユーザーからのフィードバックにより決定的な欠点があることに気づく、それを実現するためにはいくつかの修正や機能の追加が必要になる。それほど、煩雑ではないが、コードを書くときに変更に対応しやすいことを考慮に入れなかったために、すべてのコードを捨ててやり直すしかない。再度3ヶ月の時間をかけてソフトウェアを作り直す。再度新たな要件が出てきたため2ヶ月の延期だ。私たちがユーザのニーズを満たすソフトウェアを出す前に競合他社に打ち負かされる
要件の変更に対応しやすいとは
この二つの例の間であることは間違いないが、組織の文化やどのようなプロジェクトなのかによって変わってくる
コード品質の担保のための6つの柱
- コードを読みやすくする
- 想定外の事態をなくす
- 誤用しにくいコードを書く
- コードをモジュール化する
- コードを再利用、汎用化する
- テストしやすいコードを書き、適切にテストする
コードを読みやすくする
)):さらっと読んでね
ボウルを取る。これをAと呼ぶ。鍋をとる。これをBと呼ぶ。Bを水で満たし、コンロの上に置く、Aにチョコレートとバターを載せる。前者は100グラム、後者は185グラムにすること。あとはチョコレートはカカオ70%のダークチョコレートにすること。Bの上にAをおく。Aの中身が溶けるまで放置し、BからAを取り除く。新しくボウルを用意する。これをCと呼ぶ。Cに卵、砂糖、バニラエッセンスを入れる。最初のものは2個、2つ目のものは185グラム、3つ目のものはティースプーンの半分にすること。Cの中身を混ぜる。Aの中身が冷めたら、Cに移して混ぜる。新しくボウルを用意する。これをDと呼ぶ。Dに小麦粉、ココアパウダー、食塩を入れる。最初のものは50グラム、2つ目のものは35グラム、3つ目のものはティースプーンの半分にすること。Dの中身を徹底的に混ぜたら、ふるいにかけながらCに入れる。Dの中身が馴染むようによく混ぜる。そういえば、私たちはチョコレートブラウニーを作っているのだけど言ってなかったけ? Dをとり、70グラムのチョコレートチップをいれ、Dの中身を馴染むようによくかき混ぜる。焼き方を用意する。これをEと呼ぶ。Eにベーキングパウダーを塗って並べる。Dの中身にEを入れる。あなたのオーブンをFと呼ぶ。ちなみに事前にFを160度で予熱しておかなければならない。EをFに入れて20分待ってから、Eを取り除く。Eを数時間冷やす
さて、問題です。
Q. これは何の文章ですか?
Q. すべて指示通りに行うと何が完成しますか?
Q. 材料は何でどのくらい必要ですか?
番外編: ChatGpt君の回答
A. あなたが作っているのはチョコレートブラウニーですね!手順が詳しく書かれていて、素晴らしいです。以下は、手順を英語に翻訳したものです。もし何かわからない部分があれば、遠慮なくお聞きください。....(略
A. すべての指示に従って作業を進めると、チョコレートブラウニーが完成します!最終的には、濃厚でしっとりとしたチョコレートの風味が口いっぱいに広がる、おいしいスイーツができあがります。
A. ここで述べられたチョコレートブラウニーのレシピで必要な材料のリストと、それらの量を以下に示します。
ダークチョコレート (カカオ分70%):100g
バター:185g
卵:2個
砂糖:185g
バニラエッセンス:小さじ1/2
小麦粉:50g
ココアパウダー:35g
食塩:小さじ1/2
チョコレートチップ:70g
また、水を満たすために使用する鍋のサイズについては、材料の量に応じて適切なものを選択する必要があります。
読みにくい原因
- タイトルがない
- すべての節が大きな一つの塊になっている
- 溶けたチョコレートとバターが入ったボウルはAのように器具に全く関係のない名前で呼ばれている
- 必要な情報が遠くにある
想定外の事態をなくす
あなたはお腹を空かせて、ピザを取ることにしました。携帯電話をとってピザ屋の電話番号を探し、ダイヤルします。
なかなか繋がりませんでしたが、ようやく出てくれました。
あなた「ラージサイズのマルゲリータピザください」
スタッフ「あー。。。承知しました住所は?」
30分後
ピザ屋に電話していると思い込んでいたはずが、メキシコ料理店に電話してしまっていたのです。あなたの注文通話アプリは賢い機能を持っていて、ユーザーがかけた飲食店が混雑していると、別の料理店にかける機能を持ています。
開発者は利用者の80%が別のレストランにかけていることに気づいたという検証結果のもと、時間を節約できる機能をユーザのために開発したのです。
開発者はユーザーの生活をよりよくしたいと考えたにも関わらず思わぬ事態が起こるシステムを作ってしまったのです。
切り替わる瞬間に合図などがなく切り替わってしまったため、ユーザーは間違えてしまいました。この通話アプリはコードに似ています
class HelloWorld {
public static void mian(String[] args) {
System.out.println("Hello, world.");
}
}
mainがmianになっているのに気づいたでしょうか?
どうすればいいのか?
想定外の事態に警戒してできるだけ解消する
誤用しにくいコードを書く
テレビの裏を見てください。あるいは、モニターの裏でもいいかもしれません.
HDMLケースを指すときに他の電源コードの口に繋ぐでしょうか?
繋いだとしても繋がるでしょうか?
すべて同じ形をした差し込み口を持っていったらあなたは見分けがつくでしょうか?
また、正解かどうか確かめるために抜き差しをしていると燃えてしまうかもしれません
コードは、他のコードから頻繁に呼ばれるためテレビの背面に似ています。関数の引数の受け渡しや、呼び出す前にシステムを特定の状態にするなど。。。間違ったものが差し込まれれば、データベースの永続的な破損などに繋がるかもしれません。我々が作った関数などが使用者によって誤用されないようにコードを書くことが大切です。
コードをモジュール化する
モジュール化されたおもちゃ
腕や、持っている武器をモジュールだと捉える
モジュール化されていないおもちゃ
おもちゃの腕を新しいデザインに変えたくなった場合、どちらの方が簡単でしょうか?
ガンダムの場合は、パーツで分かれているため既存の腕を外して取り替える。
恐竜の場合は腕をハサミなどで切断し、何針も縫うという大手術になる。
このような自己完結方のモジュールに、ソフトウェアも分けると要件変更への対応のしやすさがあがる。さらにその単位で相互に作用する方法を明確に定義してドキュメント化している。
コードを再利用・汎用化しやすくなる
再利用性とは・・・
特定のあるものが複数のシナリオに出てくる同じ問題の解決に利用できること
ex) ハンドドリルは、壁、床のボード、天井に穴あけれる。
汎用性とは・・・
微妙に異なるものの、概念的には似ている複数の問題を解決できる
ex) ハンドドリルは、壁に穴を開けるだけでなくネジ穴を開けるのにも使える
コードを書くにも、一度書いたコードをメンテナンスするにも労力と時間が必要。
気をつけていてもバグは発生する。
コードは負債だと思って、かく量を減らすことが重要。
コードを再利用・汎用化しやすくすると多くのシナリオで利用でき、バグが減らせる。
テストしやすいコードを書き、適切なテストをする
- バグや壊れた機能を含んだコードがコードベース(develop branch)に提出されるのを防ぐ
- バグや壊れた機能を含んだコードがソフトウェアにリリースされるのを防ぐ
- ソフトウェアとコードベースは1人のエンジニアが見れないほど膨大なものになる
- 人は天才であっても間違いを犯す
テストしやすいコードはモジュール化されたコードである
テストはコードを書きあとに考慮するものではなく、かくすべての段階で必要
高品質のコードは開発スピードを遅らせるのか?
短期間だと確かに落ちるかもしれないが、中長期的に考えると上がっているはず
急がば回れの精神
- 良いソフトウェアを作るためには、高品質なコードを書かなければならない
- ソフトウェアが世に出るためには様々なチェックやテストを通過しなければならない
- これらのチェックによってバグや、ビジネス上の重大な問題を防ぐ
- 毎回のコーディング段階でテストのことを考慮するとよい。コーディング後に考慮するべきことではない
- 高品質なコードを書くことは、最初の開発速度は落とすが、中長期的には速度を上げる