# 良いコードを書く技術 〜 良いコードは良い名前と上質の苦労から生まれるんです。〜@takemioIO - これはなにか これは僕の知見をまとめたり本を読んでこうじゃないのって思ったことをdumpするコーナーです。お気持ち ## 良いコードとは何か? - 良いコードの価値と定義 - 保守性が高い - 命名規則 - スコープ - コードの分割 - コードの集約 - すばやく効率的に動作する - コードのパフォーマンス - 正確に動作する - ユニットテスト - 無駄な部分がない. - 抽象化 - メタプロ - フレームワーク ## 良いコードを書くための5つの習慣 - 読む - コードリードするためのサイト - Google Code Search - koder - 動的な読み方 - デバッガでジャンプ - unit testを実行する - これ結構良くてテストはすぐ実行可能なので別途実行のためのプログラムはいらないし、そもそも使い方が書いてる - 修正して実行する - 改変してやるやつ。printfデバックぽさがあって古典的 - 部分的にコードをコピーする - pythonとかだとREPLとかで関数単体だけを実行とかでもいいかも - 静的な読み方 - 検索コマンドを使う - IDEで関数定義とかに飛ぶ - 書く - コードを書く癖がありますか? - 道具を磨く - エディタ - vimとかプラグインとかカスタマイズ - 自動化 - テスト自動化 - DBマイグレーション - rakeやant等 - バージョン管理 - git - hg - subversion - os - unixの哲学をお前は知っているか? - 知る - 書籍 - 原典とHowTo本を買うのがおすすめ - リファレンスと仕様書のドキュメント - RFCなど - webサイト - RSSなどで効率的に巡回をする. - Google Readerなど - 聞く - 聞くということはアウトプットと対になっている動的な行動 - コードレビューを受ける - ブログを書く - アウトプットをする ## 名前付け - 良い名前の条件 - 説明的で意味や意図を表している - 省略のコツ - 先頭の数文字を残す,複数形の場合は最後のsは残す(ことが多い)(Separators->Seps) - ingの削除(Grouping->Group) - 単語の削除(formattedConsumptionTax -> fomatterdTax) - 語頭以外の母音を削除(image->img) - 強い音を残す(server->svr) - 一般的な略語の利用 - 略語に自信がないときはAcronym Finderとか使う - https://qiita.com/Koki_jp/items/f3d3e824f98d182d4100 - 一貫性がある - 対称性を保つ - begin/end - write/read - on/off - 単語の組み合わせ方を一貫させる - scoreAvg - scoreAverage - avgScore - どれでもいいので同時には使用しないなど. - 英語でつけられている - オレオレ単語を作らない - regist(実際には存在しない)ではなくregister - スペルミスに気をつける - 誤訳に注意 - GoogleCodeSearch等で他のプロジェクトで使われているのかを確認 - イディオムに従っている - チームや言語,フレームワークごとの慣習に従う - imo: これ初見だとわからなくない?何かいい方法ないの? - コーディング標準に従っている - いわゆるコーディング規約的な話 - https://qiita.com/Koki_jp/items/d5f1b5f277b581888a1f - 変数名 - 基本的には説明的名前をつける - userAgentではなくてunknownUserAgentにする - グローバル変数,クラス変数,フィールド変数 - 前述したようにグローバルやクラス変数などでは説明的な名前になっているのか? - つまり変数の寿命に応じて変数名を変えるべき - メソッド名 - 取得・getXxx - 設定・setXxx - 生成・create/build/make/genarate - 初期化・init/initalize/setupXxx - 破棄・destroy/dipose - 状態・contains/exists - https://qiita.com/Ted-HM/items/7dde25dcffae4cdc7923 - クラス名 - クラスのボキャブラは経験値則がある - OSSを読もうな・・・ - パッケージ・namespace名 - google guiceなどを参考にすべき - プロジェクト名 - イメージのしやすいコンセプトが明確なものをつけるべき - 課題管理ソフト→backlog(スクラムを連想できる ## スコープ - スコープとは - 見える範囲,使える範囲,依存する範囲,保守性に影響を与える範囲 - スコープを小さくすることを目指そう - 変数のスコープ - ローカル変数 - 一時的に使える変数 - できるだけ小さくするべき - 本当に必要になってから変数を宣言すべき - メソッドに抽出する - イテレータの一時変数はループ内に使う - 代入されることない変数はconstやfinalなどimmutableを使うべき - フィールド変数 - インスタンスごとに保持される変数 - スコープはprivate/proteced/publicなどが挙げれる - 基本的にはprivateにしてsetter/getterからのアクセスにしてあげる. - クラス変数 - クラスが保持する変数つまり設計図に対して直接書き加えることができる変数 - スコープはprivate/proteced/publicなどが挙げれる - 基本的にはprivate or immutableみたいな運用が多い - methodのスコープ - メソッドの情報量 - 基本的には少なくするべき - 社員IDを欲しい時は社員IDを渡すべきで社員オブジェクトを渡してやるのは良くない. - そうすることでIDのみへの依存となる,一方社員オブジェクトはIDや年齢などを含んでいるので依存が大きくなる.よって少ない情報量であるべき - 引数が多くなりすぎる場合はメソッドの引数は最小限にしてオブジェクトを渡すべき.または関数の分割をする - コールされる上位の関数ではオブジェクトを渡すが,その中でコールされる関数にはオブジェクトの中身のみにする - クラスのスコープ - ライブラリなどの場合はpackage内のみで使えるようにするなどの方法やnamespaceを切るなどがある - innerクラス - クラス内に定義されたクラス - 外部から使いたい場合はprotectedなどにする - 無名クラス - 再利用を考慮しない場合でスマートに描きたい場合 - キャストを使った可読性 - 不要なメソッドは見えないようにすべき - より大きい粒度のスコープ - アーキテクトレベルでの検討をする必要が出てくる ## コードの分割 - 2つの方向からの分割 - トップダウン方式 - 必要なクラスなどメソッドを洗い出して分割する - 洗い出し方 - UML - ホワイトボードに書き出す(なんかメソッドがありそう - テスト駆動開発のようにクラスの使われ方から洗い出す - ex - あるものを使うときはどう使うのだろうか - これが追加された場合はどうなるのだろうか - 使い方==外部インターフェースを決定 - ボトムアップ方式 - 書いたあとに分割する - 実装コードを書いたあと動くようになった時点で分割していく。 - XMLを返す・web apiの処理分割 - クラス図を書く・べた書きで書く - 共通処理をメソッドに抽出して分割 - どこまで共通化するべきなのだろうか。 - 変数の状態を変更するメソッドは副作用があるので避けるべき - 外部に対して副作用がないメソッドにすべき - できるだけコールするときにシンプルに行う必要がある。 - 処理単位で分割するべき - 制御構造と処理の分け方 - ループとループの中の処理 - ループの中では配列の中身を一軒ずつ処理することが多い - ループの制御構造 - 一件の要素を処理するメソッドに分割できる 具体例 ``` //まとまってないコード for (Item item : items){ if(item.isNotEmpty()){ //anything code } } //まとまったコード for (Item item: items){ process(item) } private void process(Item item){ if(!item.isNotEmpty()){ return; } //anything code } //loop 以外でも使える process(getLastItem()); ``` のように改善する - if文とその中の処理 - 長い処理は別メソッドへ分割する - try-catch-finallyとその処理 - かなり色んなパターンがあると思う。例えばexceptionのパターンで返してエラーを処理するとか - 状態を持つ処理をクラスに抽出 - インナークラスとしての意味 - 実際に使われるものの近くにクラスがあるのでわかりやすい - 元のクラスの依存関係をstaticで制御できる - 新しいファイルを作らずにできる - 自分都合なコードが書ける - 必要なデータをコンストラクタに渡すのはなぜ - テストのやりやすさを上げるため - 依存を減らす - フィールド変数として取り扱うべきは何? - フラグなどの状態を持つ変数 - 複数のインスタンスメソッドで使用するデータ - いくつかの値を組みで使う必要がある変数   ## コードの集約 - メソッドに抽出してまとめる - 重複コードをメソッドにまとめる - 継承でまとめる - 親クラスの肥大に注意 - 単一継承の制限 - ユーティリティクラスにまとめる - 基本的には状態を持たないstaticなmethodで定義 - utility classの中でDBアクセスをしたりとかある場合はサービス層やオブジェクトに対してまとめることを検討すべき - stringsなど名詞をそのまま使う or stringUtilsとかになる(utilが後ろにつく) - サービス層にまとめる - コントローラー -> サービス -> データアクセスみたいなレイヤー構造のアーキテクチャにおける中間層 - 異なるアクションクラスやコントローラークラスから共通的な抽象化されたサービス層を呼べる - オブジェクトにまとめる - 別のオブジェクトをガッツリ参照することの多い処理はそのObject自身にメソッドがあると良い - 例えばファイルシステムのフラグがあるようなクラス自身にis_exist(存在確認)できるメソッドがあったほうがいいとか - 定数にまとめる - 定数を処理に直書きするのではなくて変数においてから利用する - 可読性が上がる - まとめすぎ辛い - 一見似てるだけでじつは違うと言うコードがあるので地雷 - 気持ち的にはほぼ同じ!って言い切れるコードが2回出たらまとめるぐらいの気持ちで行くとやりすぎはなさそう ## コードパフォーマンス - 測定に始まり測定に終わる - パフォーマンスは計算量で決まる - アルゴリズムの選択によって決まる - クラスの選択で決まる - listの選択で決まる - ArrayList - 内部でリストを用いて管理 - 途中で要素の挿入されたり除去されたりすると配列の並び替えが発生して遅い - indexの指定しての参照は高速 - LinkedList - 内部的に参照リストを用いてリストを管理。 - 途中での要素挿入・除去の処理が高速 - indexを指定しての参照が参照リストだから遅い - ライブラリの使い方で決まる - 使用範囲での大きさでいろんなものが決まる - パフォーマンス・チューニングの手順 - まずは測定する - ログ出力 - 古典的で簡単 - unittestもできて何度も試せる環境があると最高 - プロファイラによる想定 - ボトルネックの特定に便利 - 作業手順フロー - 回帰テスト(チューニングの結果でプログラムの整合性が壊れてないかのためのアレ)-> - 測定-> - (問題特定->チューニング->再測定)ここを繰り返す - ->再度回帰テスト - SQLとかだとEXPLAINとか貼って特定とかする - アルゴリズム選択以外のチューニング方法 - SQLやテーブル設計の変更 - DBデータのキャッシュとか(いわゆるメモ化) - インフラを強化する - 鯖のメモリを増やす、CPUを上げる、台数を増やして分散処理、NVMeとかにする - パフォーマンス・チューニングの指針 - どのタイミングでやるべきかは割とケースバイケース。チームの温度感があるのでエンハンスのタイミングに合わせるとかが無難 - 保守性を損なわずパフォーマンスが向上する場合はそれを今すぐ選択すべき - 例えばさっきのarraylistの話とか - パフォーマンス劣化がシステムのクリティカルになる問題になる箇所は早期に計画すべき - このようなところは初期段階で洗い出せたら最高 - 特にシステムのコアの部分はやっておくべき(例えば検索システムを作っていてる場合の検索速度とか) - 適切な量のテストデータを用意しよう - はじめは小さいのでも間に合ってくるけど大量のデータのCRUDをして死ぬとかあるのである程度データを用意するのが良い - 常にパフォーマンスを意識しよう - たとえばライブラリはベストプラクティスに従うとか ## unit test - testはお好きですか? - unittestとは - いわゆる単体テスト - メソッドや関数、クラス単位での実装に対して行われるテスト - unit testの効能 - 網羅的なテストの自動化 - 回帰テストによりコードの壊れていないことを保証する - CIとかで定期実行してあげるとよさおす - ある種の安全装置 - 設計の改善につながる - テスト対象のコードが目的の場所で使われること保証 - テストコードから実行できるということをしなくてはいけないので**再利用可能なコンポーネント単位にコードを書くことになる** - ムダのないコードになる(TDDなどを行うことで実質的な設計をすることになるので) - example:web appのテスト - DBにテストデータを登録する - 画面の実装 - 画面のユニットテスト - (正常系) - (異常系) - unit testの指針 - テストのポリシーを決める - どのレイヤーに対してどの程度までのテストをするのか - テストの重複などをしなくて良くなる - テストしやすい部分は - 状態を持たない処理 - テストしにくい部分 - データベースにアクセスする処理のテスト - 他のAPIに接続するテスト - ファイルシステムを取り扱うテスト - メール送信等のテスト - スレッドなどの並行処理のテスト - 初期データのセットアップ - DbUnitとかそういうやつを使う - 場合によっては良さげなのがないから辛い・・・ - モックオブジェクト - OS,FS,APIなど環境に依存するのはテストしにくいので期待したデータを持ってるテストを返すものを書く ## 抽象化 - 抽象化とは? - 要は似ているものを同じように取り扱うこと - 例えばadd,sub,div,mulなどは四則演算なので四則演算クラスみたいなのでバンドルしてあげるとか。(いい例が出てこない:-( - 一般化すると - 共通処理をまとめて親クラスに作成 - DBの接続情報を設定ファイルとして外に出す - 似たmethodをインターフェイスとする - 抽象化にはレベルがある - パラメータレベル - 型システムレベル - ポリモフィズム(この場合はクラスとか - 配列やコレクションを使ったケース - forではなくてmapにするとか、高階関数などをつかう - 処理をlistに集めてfor eachしてあげる - 重複する処理が減ってバグが減る - 全体に影響する処理の切り出しや変更追加が簡単 - コードの行数が減ってるのに可読性が下がらない - 対象が増えてもコードの追加が既存の一行とかの変更とかで済む場合が多い - 無意味な抽象化は避ける - 可読性が死ぬ - DRYとYAGNIを守ろうね - トップダウンで考えてみる - まずはベタなコードを書いてみる - 重複の洗い出し - 可読性を高めるためにメソッドの抽出 - このときに固定を前提で考えられていたけど何個か使われる場合がある可能性がある - 抽象化してケースを追加するだけで機能が足せるようにすべき - 関連するデータに使ってるデータ構造を整理 - フィールド変数がいくつも増えて渡していると言う状況が何度か出てきた場合それはデータ構造を作って一つのObjectにするのを検討すべき - 配列・コレクション化して抽象化する - 抽象化の方針 - 度のタイミングで抽象化するのがベストなのか - なんて先まで読めるか - 将来の変更がどれくらい予想できるのか - 早すぎる抽象化やムダな抽象化は可読性が死んで保守がつらくなる - 抽象化がその後どれくらい貢献したのかを見てみるとやるべき抽象化への勘が鋭くなる - コードの重複が必ずしもアクではない - 前述してる気がするが一見似てるだけでじつは微妙に違うとか。 - たまたま今はその処理なんだけど今後は似てる2つは似てないようなコードというぐらい違うものが追加される可能性があるとか - よくある例として追加画面と編集画面の機能が似てるけど違うって話とか。 - 求められてる仕様自体が違うと言う場合が多々ある - これって単なるループでは? - 複数のデータでプログラムの構造を抜き出すことが大切 - この場合はmapとかさっきの例が良さそう ## メタプロ 使いやすさのラインが難しい - あるあるネタ - コードの自動生成 - DSL - ルーティング情報を外部に出すとかにつかう - リフレクションAPIで変更ルールを動的にする - たとえばdatetimeとかでやってあげる - DSLのテスト - いわゆる外側も含めたunittestを書いてあげると良さそう - DSLのデバックは辛い - のでテストを書いてステップ・バイ・ステップでやってあげると嬉しい - 同じようにプログラミング言語はそんな感じで作れる ## 参考 - 書籍 - 良いコードを書く技術 - クリーンコード - リーダブルコード