はじめに
「大きなクラスを小さなクラスに分割する」
「行数の多いメソッドを複数のメソッドに分割する」
「小さいは正義」「小さいことは美しい」
プログラミングを始めてから常々教え込まれてきたこの設計思想が
どうやら必ずしも正解ではない事が最近わかりました。
A Philosophy of Software Design
A Philosophy of Software Design
様々なブログや登壇等で紹介されている
(未だに翻訳されないのが謎ですが)本書に
これまでの刷り込み(?)を覆す章がありました。
Deep ModuleとShallow Module
インタフェースが複雑で中身は浅いShallow Moduleより
インタフェースが簡潔で中身が深いDeep Moduleの方がシンプル
特徴的な例としてJavaとUNIX(C)のI/Oが紹介されています。
JavaはSmall Class、Shallow Module設計のため
ファイルをOpenするだけでも以下の手順を踏む必要があります。
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BuffferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjetInputStream(bufferedStream);
一方UNIX(C)は
file = fopen(filename, "r");
IFはファイルパスとopenモードのみで大変簡潔です。
もしかしたら冒頭の刷り込みは
Java歴が長かったことに起因しているのかもしれません。
以上を踏まえてメソッド分けの基準を検討
以下からは完全に個人的な解釈となりますのでご了承ください。
同じ記述が2箇所以上にある
言うまでもなく。
引数と処理内容に規則性がある
こちらはプログラミング教室の生徒さんに気づかされた点。
「場所を移動しただけ」「メソッドの中でも同じことをしている」だったら
メソッド分けの効果は疑問符が付くでしょう。
(教育用の言語Processingかつ単純な例で失礼致します)
void setup(){
size(800,400);
}
void draw(){
background(#a0d8ef);
int param = (int)random(1,4);
int x = 0;
if (param == 1) {
x = 123;
} else if (param == 2) {
x = 345;
} else if (param == 3) {
x = 567;
}
fill(#FFDBED);
ellipse(x, 200, 100, 100);
}
void setup(){
size(800,400);
}
void draw(){
background(#a0d8ef);
int param = (int)random(1,4);
fill(#FFDBED);
ellipse(getXValue(param), 200, 100, 100);
}
int getXValue(int param){
int x = 0;
if (param == 1) {
x = 123;
} else if (param == 2) {
x = 345;
} else if (param == 3) {
x = 567;
}
return x;
}
引数と戻り値の規則性を生かして分岐を減らす事ができるのであれば
メソッドに分けるメリットがあると言えます。
float t = 0.0; //経過時間
void setup(){
size(1000,600);
background(#a0d8ef);
fill(#FFDBED);
}
void draw(){
int param = (int)random(1,4);
float x = 0.0;
float y = 0.0;
t += TWO_PI/180.0;
if (param == 1) {
x = 100.0*sin(1.0*t);
y = -100.0*sin(1.0*t*2);
} else if (param == 2) {
x = 200.0*sin(2.0*t);
y = -200.0*sin(2.0*t*2);
} else if (param == 3) {
x = 300.0*sin(3.0*t);
y = -300.0*sin(3.0*t*2);
}
ellipse(x + width/2, y + height/2, 2, 2); //円を描く
}
float t = 0.0; //経過時間
void setup(){
size(1000,600);
background(#a0d8ef);
fill(#FFDBED);
}
void draw(){
int param = (int)random(1,4);
drawPalse(100.0*param, 1.0*param, t += TWO_PI/180.0);
}
void drawPalse(float A, float w, float t){
float x = A*sin(w*t);
float y = -A*sin(w*t*2);
ellipse(x + width/2, y + height/2, 2, 2); //円を描く
}
他の人に説明できる
いきなり精神論のようになってしまいましたが
メソッドを分けたことで定義にジャンプしては戻り、
「どう言う処理の流れでしたっけ?」
となるくらいなら、メソッドを分けないほうが賢明と言えそうです。
人間の短期記憶は7±2までと言われています。
最適解はない
類似パターンが増えてきたり
逆に機能がクローズとなったりする状況の変化に応じて
常に見直しが必要となります。
まとめ
前述の通り最適なメソッド分け、インタフェース設計は
その時点でのコード全体の状態次第であり
「20行を超えたらメソッドに分けよう」のような
単純なコーディング規約で進めるのは
ナンセンスである事を理解できました。