ゆるっと備忘録

見たものを忘れないように記録する不定期ブログ

リーダブルコードを読んだ

背景

名著であるリーダブルコードを読んだ

備忘録として読んだものを軽くまとめる
自分にとって当たり前すぎることは記述していないので、読んでない方はぜひ読んでみてください
かなり読みやすいのでオススメです

1章 理解しやすいコード

コードは理解しやすくなければいけない
読みやすくコードを書くことは、他の人が最短時間で理解できるように書くことだと言える

第1部 表面上の改善

2章 名前に情報を埋め込む

命名するときは以下のことを意識すると良い

  • 明確な単語を選ぶ
  • tmpなどの汎用的な名前は明確な理由が無い限り避ける
  • 具体的な名前を使う
  • 変数名に大切な情報を追加する
    例えばミリ秒を表すものには_msなど単位がわかるように
  • 名前の長さを決める
    短ければ短いほどいい。どれくらいの長さにするかはスコープで判断 名前にはstrのような省略形があるが、全員が理解できるなら省略系はあり
  • 名前にフォーマット規約をつける
    例えばプライベート関数は小文字から始めるなど フォーマットを決めることで情報量が多くなる。チームごとに決めると良い

3章 誤解されない名前

誤解されない名前にするために以下のことを気をつける

  • 他の意味に捉えられない名前にするべき 例: filter。これだと選択なのか除外なのかわからない。selectかexcludeにするべき
  • 数値周りの命名
    限界値を指定するときはmax, min
    範囲指定はfirst, last
    包含・排他的範囲はbegin, end
  • ブール値はis,has,can,shouldをつけることが多い
  • 名前を否定系にするのは避けた方がいい
  • get* というメソッドは軽量なものという暗黙な常識がある
    大量にデータを扱って計算する場合はcomputeとか別の名前にする
  • 名前を検討するときは複数出して、他人が見たらどうかを考えて決める

4章 美しさ

美しさとはコードの見た目の美しさのこと
余白、配置、順序に関することになる

3つの原則がある

  • 読み手が慣れているパターンと一貫性のあるレイアウトにする
  • 似ているコードは似ているように見せる
  • 関連するコードはまとめてブロックにする

重複する処理はメソッド化すると、簡潔になり、処理が何をしたいか明確になりやすいかも
ヘルパー関数を作成するつもりでいると良いかも

コードの並びが処理に影響を与えない場合は、意味のある順番にする

  • 他で並んでいた順番と同じにする
  • 最重要なものから重要度順
  • アルファベット順
    など

コードは意味のあるまとまりにして、段落分けを行う

5章 コメントすべきことを知る

コメントの目的は書き手の意図を読み手に知らせること

コメントに書くべきではないこと

  • コードからすぐに理解できる
  • ひどいコードの補助(この場合はコードを変える方が良い。ひどい命名も同じ)

コメントすべきこと

  • 自分の大切な考えを記す
  • あとで改善したいTODO
  • 定数の場合はなぜ定義したか記録すべきことが多い
  • 他人に質問されそうな箇所
  • 間違った用途で使われそうな箇所
  • 全体像へのコメント
  • 関数内の、処理の塊の概要をコメント化(関数に分割できるならした方がいい)

6章 コメントは正確で簡潔に

コメントで意識すると良いこと

  • コメントは簡潔にする
  • 曖昧な代名詞は避ける
    「〇〇によって優先度を変える」ではなく「〇〇の優先度を高くする」というように直接的な表現にする
  • 時には実例をコメントに書くことでわかりやすさが上がる
    「listを逆順にソートする」という書き方より「値段が高い順にソートする」の方がわかりやすい。つまり高レベルの言い方を使う
  • 情報密度の高い言葉を使う
    キャッシュ、正規化、ヒューリスティックブルートフォース、ナイーブソリューションなど
    用語を覚えて使いこなす

2部 ループとロジックの単純化

7章 制御フローを読みやすくする

if文について

if文内の式のオペランドでは

  • 左側を調査対象(値が変化する)
  • 右側を比較対象(値が変化しづらい)

という風にすると良い

if/else文の条件については下記を意識すると良い

  • 条件は否定形より肯定形を使う
  • 単純な条件を先に書く
  • 目立つ条件を先に書く

他に意識した方がいいこと

  • 三項演算子は簡潔で読みやすくなる時のみ使い、基本はif文を使用する方がいい
  • do/while文はわかりづらいので使用を避ける
    他にも条件が式よりも後ろに来る文は避けた方がいいかも(感想)
  • goto文は基本使わない
  • ネストはなるべく浅くする
    if文でネストを作らずにすぐにreturnを返すなどする

8章 巨大な式を分割する

コードの塊は小さくするべき。下記のことを意識すると良い

  • 式を変数に代入する
    式の説明として変数名を活用
    式の要約として変数名を活用
    何回も登場する式は要約変数にすると良い
  • 論理式はドモルガンの法則でわかりやすくできるかも
  • 複雑なロジックになる時は、そのロジックの反対を考えて簡潔にできないか試す

9章 変数と読みやすさ

コードを読みにくくする変数もあるため、それは削除するべき
下記のような変数はない方がいい

  • 複雑な式でもなく重複の削除にもなってない変数
  • 中間の結果を保持するための変数
    結果は保持するのではなくすぐに使う
  • 制御フロー変数

変数のスコープはできるだけ短くするべき
そのために下記を意識すると良い

変数の値が変更される箇所は少ない方がいい
変数は一度だけ書き込む状態が好ましい

第三部 コードの再構成

10章 無関係な下位問題を抽出する

エンジニアリングは大きな問題を小さな問題に分割して解決するべき

  • 無関係な下位問題は別の関数にした方が良い
    例: 2地点の配列が与えられた時、最も距離が短い2地点を算出する関数をつくる。その時、2地点間の距離を求める部分は無関係な下位(だけど必要な処理)だと言える
  • どの場所でも使う関数があればユーティリティコードにする(例えば時間の変換など)
  • 汎用的な下位問題は抽出し、別の関数にする方が良い
    ただ、関数を別にしすぎると様々な関数を見にいく必要が出るため、やりすぎは注意

11章 1度に1つのことを

1度に複数のことを行うコードは分かりづらいので分割するべき
コードのタスクを列挙し、ジャンル分けをして、順番にタスクが行われるように整理する(必要であればタスクを別の関数にする)

12章 コードに思いを込める

理解した状態とは、相手に簡単な言葉で説明できる状態
コードも簡単な言葉で説明できるようにしておくべき

ロジックを簡単に説明してからコードを書き始めるといい
言葉にしたものをコードにすれば分かりやすくなるし、良い変数名を考えられる

13章 短いコードを書く

コードは書かないに越したことはない
コードを書くと、テスト・文書化・保守が必要になる

コードを書かないために

  • 不必要な機能はプロダクトから削除する(なぜあるのか、影響はあるのか調べる手間をなくす)
  • 最も簡単に解決できるようなアプローチを考える(例:経度緯度を計算して店舗検索するのではなく、現在位置の30m以内を店舗検索する方が実装しやすい)
  • 標準ライブラリに親しんでおいて、利用することで自前の実装を省略する

4部 選抜テーマ

14章 テストと読みやすさ

テストは読みやすさが重要。そうなればテストの追加や修正がしやすくなる

  • テストのトップレベルは極力簡潔にする。テストに必要な値の準備などは関数にまとめちゃっていい
  • テストでエラーが起こったときに、どんなエラーだったか詳細がわかると良い
  • テストケースは単純な値や組み合わせがいい。1つのテストケースを複雑にするなら、単純なテストケースを複数作る方がいい
  • テストケースは、エッジケースや極端な値を設定するケースもあるとなおよし
  • テスト関数名は長くてもいいので、何をテストするのかわかるようにする

実装本体のコードはテストがしやすいように書かれた方が、疎結合でわかりやすいコードになりやすい
ただし、テストコードを意識しすぎて本体コードに余分な処理を入れたり、時間がよりかかったりすると元も子もないので気をつける

15章 「分/時計カウンタ」を設計・実装する

実例だったが、C++だったのでよくわからなかった

クリーンアーキテクチャ本を読んだ

背景

Robert C. Martin 著作の「Clean Architecture 達人に学ぶソフトウェアの構造と設計」を読んだ

ものすごく良い本だったなぁと思うと同時に、少しでも記憶に定着させるためにアウトプットしようと思って記事を書く

要するにこういうこと言いたいんだろうなという自分の解釈を書くだけなので、厳密に違ったとしても容認していただきたいです
読んだことがない方はこの機会にぜひ!

ここで記述する引用は全て「Clean Architecture 達人に学ぶソフトウェアの構造と設計 著 Robert C. Martin, 訳 角 征典 訳, 高木 正弘」からのものとします

本の結論

これから本に書いてあったことを簡単にまとめながら記すが、全体的に何が言いたかったねんというのをここでまとめておく

  • ビジネスロジックが上位、DBやUIが下位で、コードは常に上位へ依存する
  • 境界を作ることでコードの保守性が上がる

アーキテクチャとは

労力を少なくシステム開発できるようにすることを目指す、ソフトウェアの設計。以下、引用

ソフトウェアアーキテクチャの目的は、求められるシステムを構築・保守するために必要な人材を最小限に抑えることである。


アーキテクチャを常に意識してコードを書く方が良い。理由は下記の2つ

  • 短期的に早く実装するよりも、綺麗なコードを書いていく方が長期的には生産性が高い
  • あとでキレイにするという考えは、結局やらない(常に新しい開発が求められるから)


良いアーキテクチャにすることでソフトウェアの価値が上がる
ソフトウェアの価値は振る舞いと構造の2つ

  • 振る舞い: システムが要件に沿った動きになっているかが価値につながる
  • 構造: 柔軟にシステムを変更できるかが価値につながる

要件通りの動きをしても変更できなければ意味がないので、構造も重要だと言える

設計の原則

SOLID原則というものがある。
これは変更に強く、理解しやすく、多くのソフトウェアで適用できる設計原則

モジュールレベルの開発が対象となる
モジュール・コンポーネント・サービスといった単位が出てくるが、それぞれがどれくらいの範囲なのかはいまいちわからず...

S: 単一責任の原則(Single Responsibility)

モジュールの責任は1つにするべきという原則(1つの変更の影響範囲が広がらないように)

言い換えると、モジュールはたった一つのアクターに対して責務を負うべきという原則

  • モジュール: いくつかの関数やデータをまとめた凝集性のあるもの
  • アクター(ユーザーやステークホルダー

複数のアクターへ影響が出るようになっていると、コードを修正するときに影響範囲が広がってしまう
データを関数から切り離したり、Facadeパターンを利用したりして、コード変更時に複数のアクターへの影響を避ける

O: オープン・クローズドの原則(Open-Closed)

既存コードの変更よりも新しいコードの追加で振る舞いを変更できるようにするべきという原則(それによりバグの発生を減らす)

言い換えると、ソフトウェアは既存コードを変更せずに拡張できるようにするべきという原則
拡張のたびに既存コードを変更する必要があると、拡張のコストが大きくなってしまう
この原則を守るために、システムをコンポーネントに分割して依存関係を階層構造にし、インターフェースを利用することで上位レベルは下位レベルの変更を受けないようにする

L: リスコフの置換原則(Liskov Substitution)

交換可能なパーツを使った場合、交換可能となるように従うこと(クラスの継承で、小クラスは親クラスの振る舞いをできるようにしなければならないというイメージ)

親クラスから派生されたものは、親・兄弟ともに置換できるようにするべきである
置換可能になっていないとそれぞれの型に依存することになり、処理が複雑化してしまう

I: インターフェース分離の原則(Interface Segregation)

使っていないものへの依存は回避すべきという原則

クラスを利用する時はインターフェースを通じて、必要な動作のみを利用するみたいなイメージ
そうすることによって利用していない部分の変更の影響を受けず、その部分を変更したことによる再デプロイも避けることができる

D: 依存性逆転の原則(Dependency Inversion)

上位レベルは下位レベルの詳細の実装コードに依存するべきではないという原則
また、詳細は方針に依存すべき(Controllerがinteractorに依存するのではなく、interactorがinputportに依存するみたいなイメージ)

依存性逆転については本の中で頻出するので、最も重要な原則な気がした
参照先は抽象クラスやインターフェースに限定するべきで、具象なものには参照せず、継承も行うべきでない

コンポーネントの原則

コンポーネントとは、システムの一部としてデプロイできる最小の単位のこと

コンポーネントの凝集性に関する原則

どのクラスをどのコンポーネントに含めるべきかという凝集性に関する原則は3つある

  • 再利用・リリース等価の原則(REP)
    再利用の単位とリリースの単位は等価であるべき。コンポーネントを形成するクラスやモジュールは凝集性あるものであるべきで、まとめてリリース可能であるべき
  • 閉鎖性共通の原則(CCP)
    同じ理由・同じタイミングで変更されるクラスはコンポーネントにまとめる。逆の場合は別にする。単一責任の原則のコンポーネント
  • 全再利用の原則(CRP
    利用しないクラスを持つコンポーネントに依存するべきではない。コンポーネントにあるクラスは密結合であるべき。インターフェース分離の原則のコンポーネント

コンポーネントの結合に関する原則

  • 非循環依存関係の原則(ADP)
    循環依存があるとビルドやテストの対象コンポーネントが広くなってしまうため、省く必要がある
    インターフェースを作成するか、依存される箇所を別のコンポーネントとして作成することで循環依存は解消できる コンポーネントトップダウンから設計できればいいが、初めからそれを行うのは不可能なため、柔軟に対応していく方がいい
  • 安定依存の原則(ADP)
    安定とは依存するより依存されることの方が多い状態のこと。つまり変更がしづらい状態のこと
    変動を想定したコンポーネントは、安定したコンポーネントから依存されてはいけない(変動するたびに安定したコンポーネントに影響があるから)
    コンポーネントに安定を求める必要はなく、安定していないものから安定しているものへと依存している状態であれば良い
  • 安定度・抽象度等価の原則(SAP)
    抽象度とは、全てのクラスの中で抽象クラスとインターフェースはどれくらいあるかを表す度合い
    抽象度が高く安定性もあるもの、または抽象度が低く安定性がないものは望ましい。安定度だけが高いと拡張が難しく、抽象度だけが高いと無駄な抽象クラスが存在する可能性がある。安定度と抽象度のバランスを考えることが重要

アーキテクチャ

ここからが本題
アーキテクチャによってシステムのライフサイクルをサポートする
優れたアーキテクチャであれば、システムを容易に理解・開発・保守・デプロイできる

システムを開発するにあたって、方針と詳細を切り離された方がいい
例えば、印刷をしたいときにどのプリンタを利用するかという詳細を決めるべきではない。特定のプリンタに依存すると変更が効かなくなる
初めに方針だけを先に考えて、詳細はあとで決めることで、情報が揃った状態でどのようなサーバーやDBを使うかなどを決めることができる

アーキテクチャが懸念すべきこと

  • ユースケース: システムの意図がわかるように振る舞いを明確にするのが重要
  • 運用: 特定の技術に依存せず、変化に強い状態が好ましい。コンポーネントの適切な分割が大事
  • 開発: 各チームがお互いに干渉しないように開発できる状態が良い(コンウェイの法則)
  • デプロイ: 構築後即時デプロイできる状態が好ましい。コンポーネントの適切な分割が大事

まとめると、コンポーネントを適切に分割し、様々な選択肢を残す状態にしておくのが良い

様々な選択肢を残すためには切り離しを行う必要がある

  • レイヤーの切り離し: UI・アプリケーション特有のビジネスルール・アプリケーションに依存しないビジネスルール・データベースなど
  • ユースケースの切り離し: 注文の追加・注文の削除など

切り離しはソースコードレベル・デプロイ(バイナリ)レベル・サービス(実行単位)レベルの3つのレベルがあり、柔軟にそれぞれのレベルに変えられるようにしていくと良い

境界線を引く

マンパワーを奪うのは早すぎる決定との結合である
早すぎる決定とは、ビジネス要件が定まらないうちにDBやウェブサーバーを決めること

ビジネス要件と技術に境界線を引くことでこの決定を延期できる
どの技術を使うかを後で決めて、特定の技術に対する考え事を減らすこともできる

境界線は重要なものと重要ではないものに引く。例えばGUIはビジネスルールとって重要ではない。DBはGUIにとって重要ではないなど
ビジネスルールはデータベースと境界線を引き、データを取得・保存できるインターフェースがあるということだけ知っとけばいい(こうすることでさまざまなDBを利用できる)
また、IOはシステムとは無関係なので、GUIからシステムを構築するのは悪手だとも言える

境界線は変更の軸があるところに引く。つまり変更の理由や頻度が異なるところに引くと良い

境界にはレベルがあり、モノリス内のソースレベル、デプロイレベル、サービスレベルの3つがある サービスレベルの場合は、サービス間で通信が行われるためレイテンシーを気にする必要がある

方針とレベル

下位レベルから上位レベルへの依存となる状態が好ましい
入力・出力から離れているものほどレベルが高いと言える
入力・出力はインターフェースを使って、上位レベルから切り離すべき

ビジネスルール

ビジネスルールとは、コンピュータで実装されていなくてもビジネスマネーを生み出したり節約できたりするもの(自動化しなくてもいいものとも言える)
例えば銀行がローンに利子をつけるときは、コンピュータでなくてもそろばんでもいい
これを最重要ビジネスルールと呼び、そのために必要なデータ(金利・支払いスケジュールなど)は最重要データと呼ぶ
ビジネスルールはシステムの中で最も独立しているべきである

エンティティ
システム内部にあるオブジェクトで、最重要ビジネスデータを操作する最重要ビジネスルールをいくつか含んだもの ビジネスにとって不可欠な概念をまとめて、システムとは独立させる。ビジネスそのものという感じ

ユースケース
アプリケーション固有のビジネスルール。自動化した部分とも言えそう
新規ローン作成を例にした場合、連絡先情報がシステムへと入力 -> 与信スコアが生成 -> 与信スコアが基準値を超えない限り支払い見積もりの画面に進まないという流れががユースケース
データどのように入出力されるかは無関係
ユースケースよりエンティティの方が上位になる

叫んでいるアーキテクチャは良い状態

アーキテクチャによってそのシステムが何を表しているのかわかると良い
そのために、ユースケースを中心とするようにするべき。どの技術を使うとかは詳細なのでユースケースとは切り離されるべき

建物の設計を例に出すと、設計図だけで図書館の設計とわかるようになっていると良い状態
ソフトウェアも同じだと言える

クリーンアーキテクチャ

この章は最も重要だと思う
翻訳もネットにあるので載せておく

クリーンアーキテクチャ(The Clean Architecture翻訳) | blog.tai2.net

さまざまなアーキテクチャがあるが、それらはどれも関心ごとの分離をして、レイヤー分割をしている

様々な良いとされるアーキテクチャには下記の特徴がある

  • フレームワーク非依存: ライブラリやフレームワークに依存せず、それらをツールとして利用
  • テスト可能: DBやUIといったものがなくてもビジネスルールをテストできる
  • UI非依存: UIはシステムの他の部分を変更することなく簡単に変更できる
  • データベース非依存: DBにビジネスルールは依存していないので簡単にDBを変更できる
  • 外部エージェント非依存: ビジネスルールは外界のインターフェースを知らない

依存性のルール エンティティ <- ユースケース <- インターフェイスアダプター <- フレームワークとドライバー
という順番でエンティティに向かって依存の方向が向く
内側は外側に変更があったとしても何も影響がない

また、プレゼンターの仕事はアウトプット用のデータをView用の形式に変換することである

プレゼンターとHumble Object

Humble Object パターンとはテストをしやすい部分としにくい部分を分離するデザインパターン
これを利用することでテスト容易性が向上する

例えば、GUIはテストしづらいが、GUIに渡すデータをフォーマットするPresenterはテストしやすい

Humble Object パターンを利用することは、アーキテクチャの境界の特定と保護を行うのに役立つ

部分的な境界

アーキテクチャに完全な境界を作るのは難しい。特にシステムが初期の場合
対策として、部分的な境界を作って対処する(YAGNIに反することにはなる)

方法

レイヤーと境界

UI・ビジネスルール・データベースの3つで境界を引ければ簡単だが、実際はそんな簡単なものではない
UIでもGUIやコンソールがあるし、英語や日本語表示もある

部分的に境界を実装するべきか無視するか推測する必要があるし、1回限りの決定ではなく常に見張って、後で境界を実装する必要もある

メインコンポーネント

Main コンポーネントは他のコンポーネントを作成・調整・監督するコンポーネント
最下位レベルに位置し、初期状態や構成の設定・外部リソースの収集をして、上位レベルに制御を渡す
クリーンアーキテクチャの最も外側に位置する

サービス:あらゆる存在

サービスとはアーキテクチャではない
振る舞いを分離しただけのもの
プロセスやプラットフォームを超えた関数呼び出しにすぎない

サービスはデータやリソースを共有しているし、大規模なシステム開発に利用されている
しかし、データ・リソースの共有や、大規模なシステム開発ができるということに関して特別メリットがあるわけではない


サービスを作ることがアーキテクチャになるのではない

例えば、タクシー配車アプリで、急に子猫配送機能をつけるとなった場合、全ての動作に影響を与える横断的変更が起こる
それを阻止するためにも、サービスは内部コンポーネントアーキテクチャを設計し、依存性のルールに沿うことで派生クラスを生み出しやすいようにする

テスト境界

テストはアーキテクチャに関与している
ソフトウェア設計の第一のルールは変化しやすいものに依存しない。なのでテストもGUIのような変化しやすいものに依存しないべきである

テストはテスト対象コードに依存し、テスト自体が非常に詳細で具体的なのでアーキテクチャの最も外側になる
テストがコードに構造的結合がされていると、コードを変更するたびにテストも変更しなければいけないので良くない

クリーン組み込みアーキテクチャ

ファームウェア(ハードウェアとやりとりするコード)とソフトウェアは別である

ファームウェアはハードウェアに依存しするもの
しかし、ハードウェアの進化は速いため、ソフトウェアのファームウェアに対する依存箇所が多いとシステム全体がファームウェアになる
ファームウェアではなくソフトウェアの部分を多くする必要がある

ファームウェアに限らず、コードはシステムの動作・高速化をさせるだけでなく、理解しやすい形にするべき

組み込み開発は一般的ではないUIや制限されたIO・メモリなど特別なことが多いが、開発自体は特別ではない
ハードウェア・ファームウェア・ソフトウェアの境界を定めて開発するといい
ソフトウェアとファームウェアの境界にはハードウェア抽象化レイヤー(HAL)を設置し、ソフトウェアがハードウェアの詳細を知らなくても動くようにする

マイクロコントローラーを使用するときは、低レベル機能プロセッサ抽象化レイヤー(PAL)でフームウェアから分離する
OSが存在する場合もあるが、その場合はソフトウェアをOS抽象化レイヤー(OSAL)でOSから分離する
レイヤー分けすることによって、対象のハードウェアやOS等がなくてもテストを行えるようになる

詳細

データベースは詳細

どこからどのような形式でデータを取得するかを上位は知る必要はない
データモデルはアーキテクチャ的に重要だが、データを移動させる技術は重要ではない

Webは詳細

WebはGUIであり、IOデバイスの一種と考えられる
Webに対しても適切に抽象化を行い、ユースケースから分離させると良い

フレームワークは詳細

フレームワークと結合すると、仕様変更の影響を受けたり、変更したい時に邪魔になったりする
フレームワークを使う場合はコンポーネントを別にして、プラグインとして利用するようにする

書き残したこと

コードを分割する方法はいくつかある

個人用メモ

MVPとMVCの違い

  • MVP: presenterはviewに値を渡すだけでなくメソッドも使う。viewもpresenterを利用
  • MVC: controllerはviewから呼ばれて値を返すだけ

ビジネスロジックとは

プレゼンテーション層・ビジネスロジック層・データアクセス層にアプリケーションを分けた場合、プレゼンテーションでもアプリケーションでもないもの

データアクセスとは、その名の通りファイルやDBにアクセスするもの
プレゼンテーションとは、 プログラムとユーザーとのやり取りを担当するもの。CLIGUIなどUI・APIごとに処理が変わる

「コンピュータとじゃんけんをして、その結果をどこかに保存する」というアプリケーションがある場合、じゃんけんの勝敗判定と一連の流れがビジネスロジック
参考: https://qiita.com/os1ma/items/25725edfe3c2af93d735