Top Banner
良良良良良良良 良良良良良良良
28

良いコードとは

Feb 08, 2017

Download

Technology

Nobuyuki Matsui
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: 良いコードとは

良いコードとはまついのぶゆき

Page 2: 良いコードとは

良いコードとは• (エンタープライズにおける)良いコードとは、「読みやすくて理解しやすく、修正しやすいコード」のことである

• メモリ使用量や CPU 使用量、 I/O 転送量が低いコードのことではない• 少しでも高速に動作するコードのことではない

• ゲームや特殊な環境で動作するソフトウェアなどでは、こういうコードが「良い」コードの場合もある• トリッキーな手段を駆使してなるべく短くかかれたコードのことではない

• 競技プログラミングなどでは、こういうコードが「良い」コードの場合もある

Page 3: 良いコードとは

なぜ「良い」コードを書くべきなのか• エンタープライズでは、チームで開発することが多い• 一人ですべて開発するのだとしても、3か月前の自分は他人• エンタープライズでのコードは、「書かれる」よりも「読まれる」ことが圧倒的に多い

• エンタープライズのコードは機能拡張が常に発生するため、拡張に関連「しそうな」コードは全て読まなければならない• 障害の発生時には、障害周辺のコードを素早く読まなければならない

• そのため「今動作する」ことが重要なのではなく、「読まれて理解しやすい」コードを書くことが重要となる

Page 4: 良いコードとは

「良い」コードの基本方針• 読みやすい書き方を心がける• ロジックをシンプルに保つ• コードを再構成(リファクタリング)する• テストできるように書く

Page 5: 良いコードとは

読みやすい書き方• 適切なネーミングを行う

• その変数や関数がやりたいことを端的に表現する明確な単語を選ぶ• get などの関数名や、 result といった変数名は、中身が何なのかわからない• 明確な単語に情報を付加すると良い

• ファイルサイズを格納する変数には、 uploaded_file_mb にするとか

• tmp や buf のような、汎用の名前は避ける• ただし一画面で収まるスコープに限定される変数名の場合、使っても良い• ループ変数などもスコープが限定されるため、 i や j で良い

• 明確な名前が選べなかったり、非常に長い名前を付けたくなる場合は、適切なモジュール分割ができていない• canvas_max_px って付けたくなる場合、 max_px をインスタンス変数に持つ

Canvas クラスが存在するのでは?

Page 6: 良いコードとは

読みやすい書き方• 適切なコーディングスタイルとネーミングスタイルに従う

• 言語に対してデファクトとなっているスタイルをなるべく利用し、できる限りルールを発明しない• 例えば Python なら

• PEP8• http://legacy.python.org/dev/peps/pep-0008/

• Google Python Style Guide• http://google-styleguide.googlecode.com/svn/trunk/pyguide.html

• インデントや空行も意味があるので、スタイルに従う• Python のようにインデントが制御構造に直接関係しなくても、意識して使う

Page 7: 良いコードとは

読みやすい書き方• 見た目の一貫性を重視する

• 例えば• 同じ変数を引数にする関数が複数あるならば、同じ順序にする• 同じ意味を持つ変数を複数のモジュールで定義するならば、同じ名前にする

• 読む人が「あれっ?以前でてきたアレでもこうだっけ?」とならないように

Page 8: 良いコードとは

読みやすい書き方• なるべく自分で書かない

• 言語が持っている機能やデファクトで利用されるライブラリの機能を最大限活用する• ソート処理や探索処理などは、セキュアで高速な実装が言語処理系からたいてい提供されている• 文字列処理等の誰でも欲しい機能は、 Java ならば apache commons

Ruby ならば active support などのライブラリがたいてい提供している• 言語仕様だけでなく、言語が提供する関数仕様やメジャーなライブラリ仕様は一読しておくと良い

Page 9: 良いコードとは

読みやすい書き方• 適切なコメントをつける

• 「プロジェクトルールだから」と、意味のないコメントは付けない• そのクラスやメソッドを実装しようと思った意図や設計思想をコメントする

• そもそもコレは何をするためのものなのか?• なぜこのロジックを選択したのか?代替手段はあったのか?

• 自分で微妙と思っている箇所もコメントする• 例えば

• このロジックは動作するけれど、データ量に対して計算量が O(n^2) になる• このメソッドは破壊的なので、一度呼び出すと内部データは変更されてしまう

• コードを見たらわかることはコメントしない• 処理の手続きをコメントしなければ理解できないコードは、「悪い」コード

Page 10: 良いコードとは

ロジックをシンプルに保つ• 条件分岐

• 単純で重要な条件を先に評価する• ド・モルガンの法則やカルノー図を使うことで、条件を簡約化できないか考える

• ド・モルガンの法則• not (A or B) == (not A) and (not B)• not (A and B) == (not A) or (not B)

Page 11: 良いコードとは

ロジックをシンプルに保つ• カルノー図の効用( by @kawasima )

• 例えば以下のような条件が提示された場合

Page 12: 良いコードとは

ロジックをシンプルに保つ• カルノー図の効用( by @kawasima )

• 普通に実装すればこうなるけれど

Page 13: 良いコードとは

ロジックをシンプルに保つ• カルノー図の効用( by @kawasima )

• カルノー図を書いてみるとこうなるので

Page 14: 良いコードとは

ロジックをシンプルに保つ• カルノー図の効用( by @kawasima )

• もっとシンプルな条件で実装できる

Page 15: 良いコードとは

ロジックをシンプルに保つ• 関数内からはなるべく早く返す

• 判断した条件をずっと覚えておくよりは、異常時にはさっさと関数から抜け出してしまった方が読みやすいdef construct_msg(num_pageview): msg = "" if (isValid(num_pageview)): foo() … bar() … msg = buz() else: msg = "invalid"

return msg

def construct_msg(num_pageview): if (not isValid(num_pageview)): return "invalid"

foo() … bar() … return buz()

Page 16: 良いコードとは

ロジックをシンプルに保つ• トリッキーなコードは書かない

• 言語仕様上許されているからと言って、トリッキーなコードを書いて「オレスゲー」しないxx = 1yy = 2def f(x,y): return x + y + xx + yy

globals().update({"xx":1,"yy":2,"f":lambda x,y:x+y+xx+yy})

Page 17: 良いコードとは

ロジックをシンプルに保つ• トリッキーなコードは書かない

• 言語仕様上許されているからと言って、トリッキーなコードを書いて「オレスゲー」しないdef check(x): if x%2 == 0: return even()     else: return odd()

def check(x): return odd() if x%2 else even()

def check(x): return (x%2 and [odd()] or [even()])[0]

=def check(x): return [even, odd][x%2]()

Page 18: 良いコードとは

ロジックをシンプルに保つ• 性能向上やメモリ削減のための特殊な実装は、最後の最後

• パレートの法則にあるように、性能劣化やメモリ消費を引き起こすコードは、全体のうちのホンの一部• 最初は読みやすいコードを心がける• システム全体の性能を計測し、システムの性能劣化やメモリ消費に影響の大きな箇所からピンポイントに修正する

• 本当に性能が必要なのだとしたら、ピンポイントに C で書き換えても良い• というか、 SQL チューニングが最も効果的だったりする

• ただし不用意なネストループや O(n2) の探索アルゴリズムなど、わかりきった遅いコードは最初から避けるべき※パレートの法則 「全体の数値の大部分は、全体を構成する一部の要素が生み出す」 「 8:2 の法則」

Page 19: 良いコードとは

コードを再構成(リファクタリング)する• 一画面に収まらない処理はモジュール分割できないか考える

• コードが実現したいこと(コメントとして一行で書ける目的)に直接関係しない処理は、別のモジュールに切り出す• ネーミングが適切に行われていれば、切りだされたモジュールのコードを読まずともモジュールの処理内容をざっくり理解できるので、元のコードが理解しやすくなる

• では、どのような方針で分割すべきだろう?

Page 20: 良いコードとは

コードを再構成(リファクタリング)する• the Open-Closed Principle ( OCP )

• 「ソフトウェアの構成要素は、拡張に対して開いていて、修正に対して閉じていなければならない」• OCP が意識されているモジュールは、修正しても他のモジュールに影響を与えない=オブジェクト指向設計の原則• API 仕様の重要性

• 外部に公開している API の仕様(公開されているメソッドの INPUT に対して何が OUTPUT されるか、その際に発生する副作用は何か)が変わらないのであれば、中のコードが書き換わっていても誰も気にしない

Page 21: 良いコードとは

コードを再構成(リファクタリング)する• 関数の副作用とは

• (数学的な意味の)関数=入力を出力に変換するモノ• 入力を出力に変換する以外に、関数外部の環境へ影響を及ぼす行為=副作用

• ファイルやネットワークの入出力、画面の入出力、データベースの入出力などは全て副作用• 関数外部の変数への入出力も副作用

• 副作用がない関数は、いついかなる状況で呼び出しても必ず同じ結果• テストしやすく堅牢なモジュールになる

• 関数型プログラミング言語は、基本的に副作用の無い関数でシステムを組み立て、副作用のある行為を限定することで堅牢なシステムを作る• ただし「副作用」が全くないシステムには意味が無い

Page 22: 良いコードとは

コードを再構成(リファクタリング)する• 関数型プログラミングのエッセンスを取り入れる

• 変数のスコープはなるべく小さくする• グローバル変数は敵

• 値を代入するのは一回だけ(同じ変数の使い回しや書き換えはしない)• ループ変数など小さな関数内部に限定された変数は書き換えても良い• 暗黙的に依存する変数を書き換えるような関数を作ると、思いもよらない箇所で変数が書き換わるややこしいバグを生む可能性がある• list.append() なども、変数の中身を書き換えていることに注意

• 例えばリスト内包表記を用いれば、元のリストはそのままで新しいリストが生成される>>> x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>>> y = [i * 2 for i in x if i%2 == 0]>>> y[4, 8, 12, 16, 20]>>> x[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Page 23: 良いコードとは

コードを再構成(リファクタリング)する• 関数型プログラミングのエッセンスを取り入れる

• 関数に与える引数は、関数内で書き換えない• Java や Python の関数の引数は参照渡しなので、仕様上は引数オブジェクトの中身を書き換えることができるが、基本的には書き換えてはいけない

• 関数からの出力は、すべて戻り値として返す• 例外発生時は、戻り値ではなく例外を raise する

• 結局・・・• 「外部入出力など副作用を前提とした部分」と「クラスの内部データに依存した処理を行う部分」と「副作用が全く無い関数」を明確に分割し、そのコードが影響を及ぼす範囲を局所化することが重要

Page 24: 良いコードとは

コードを再構成(リファクタリング)する• the Single Responsibility Principle ( SRP )

• クラスの役割はただ一つだけ• クラスのコメントに、「このクラスは XX をする役割を担う」と一文で表現できなければならない

• クラスの実装を変更する理由は、その「 XX の役割」に拡張や修正があったときだけのはず• 「 XX の役割の修正」以外の理由でプログラムを修正する際に、なぜか「 XX の役割」のクラスに修正が入るようであれば、クラスの分割が間違っている

Page 25: 良いコードとは

コードを再構成(リファクタリング)する• 適切なクラスを見出すために、デザインパターンを活用する

• イイカンジにクラスを分割するためのベストプラクティス集• 最も伝統的なもの: GoF ( Gang of Four )デザインパターン• GoF 以外にも、様々なデザインパターンが提唱されている

• ただし「パターンを使いたい」がためにパターンを使っては本末転倒

Page 26: 良いコードとは

コードを再構成(リファクタリング)する• コードの適切さを評価するために、メトリクスを活用する

• 凝集度と結合度• モジュールの OCP や SRP を計測する尺度• 凝集度は高く、結合度は低いのが望ましい• コードメトリクスツールで数値化することができる

• 細かい数値にはあまり意味がなく、ざっくりとした傾向を見るために用いる

• McCabe の循環的複雑度• コードの複雑さ(ループや分岐の度合い)を計測する尺度• 一般的には、 10 以下が良いと言われている

• 30 を超えると構造的に失敗したモジュールと言われており、 50 を超えるとテスト不可能で、 75 を超えると微細な修正であってもバグが混入するらしい

Page 27: 良いコードとは

テストできるように書く• 副作用のない関数は、容易にテストできる

• なるべくシンプルに、網羅的に• ただし闇雲に様々な値でテストを書くのではなく、なぜその入力でテストをするのか、明確な理由を考える

• 限界値• 期待している値の最大値を +1超えた値、最小値を -1 した値等

• 特殊値• 数値ならば 0• 文字( utf-8 )ならば、ビットパターンが 1byte になる文字( US-ASCII 文字)、 2byte になる文字(ギリシャ文字等)、 3byte になる文字(常用漢字や丸数字等)、 4byte 以上になる文字( JIS X 0213 の第 3 ・ 4水準漢字)など• 文字( Shift-JIS や CP932 )ならば、「表」や「ー」など 2byte 目が 0x5c(バックスラッシュ)になる文字や、 CP932 から UTF-8 に変換することによって化ける文字(~)等

Page 28: 良いコードとは

テストできるように書く• 副作用のある処理

• スタブやドライバを駆使してテストを書く• テストフレームワークが提供する機能をうまく活用する

• 適切にモジュール分割されテストできるように書かれたプログラムを継続的にテストし続けることで、チームで開発していても安心して機能拡張をすることができる