CODE COMPLETE
~特殊な制御構造~
エンジン開発6 鈴木優
アジェンダ
- 複数のreturn文
- 再帰
- goto文
- 特殊な制御構造の展望
- まとめ
return文
関数から複数のreturn
答えがわかった時点でreturnする
int Compare(int value1, int value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
}
return 0;
}
関数から複数のreturn
複数のエラー条件を確認する例
//ファイル名チェック
if (file.validName()) {
//ファイルが開けたかチェック
if (file.open()) {
//暗号キーが入力されているかチェック
if (encryptionKey.valid()) {
//暗号キーが正しいかチェック
if (file.decrypt(encryptionKey)) {
//正常時の処理
}
}
}
}
インデントが深すぎて読むのが苦痛・・・
関数から複数のreturn
ガード句を使って単純化しよう!!
if (!file.validName()) {
return;
}
if (!file.open()) {
return;
}
if (!encryptionKey.valid()) {
return;
}
if (!file.decrypt(encryptionKey)) {
return;
}
//正常時の処理が以下に続く
答えがわかった時点で処理を戻す
関数から複数のreturn
さらによくするなら、エラー内容をしっかり上位層に伝えてあげる
if (!file.validName()) {
return FILE_INVALID_NAME_ERROR;
}
if (!file.open()) {
return FILE_OPEN_ERROR;
}
if (!encryptionKey.valid()) {
return INVALID_ENCRYPTION_KEY;
}
if (!file.decrypt(encryptionKey)) {
return DECRYPTION_ERROR;
}
//正常時の処理が以下に続く
複数returnの注意点
各関数のreturn文は最小限に抑える
- 関数の最後の部分だけに目を通した場合、
その手前のどこかで処理が戻されている可能性に
気づかないと、関数を理解するのが困難になってしまうため
?
所感など
早めに返せるものは返してしまってよいのでは?
そもそも関数の最後のreturnだけをみるパターンって
そんなにある?
再帰
再帰の例
- 進める方向は上下左右
- 再帰を利用して迷路を抜ける
- ゴールが見つかるまですべての経路を試しながら進む
|
--------
|
|
|
|
→
|
迷路を解く
- 無限再帰を防ぐために、
すでに通った道は記憶しておく
- 元の制御に
trueが返ってきたら出口あり
falseが返ってきたら出口なし
bool FindPathThroughMaze(Maze maze, Point position) {
//既に通った道は試さない
if (AlreadyTried(maze, position)) {
return false;
}
//この地点が出口であれば、成功を返す
if (ThisIsTheExit(maze, position)) {
return true;
}
//この地点をすでに通ったものとして記憶する
RememberPosition(maze, position);
//上下左右への経路を確認する
//いずれかの経路が成功すれば、終了する
if (MoveLeft(maze, position, &newPosition)) {
if (FindPathThroughMaze(maze, newPosition)) {
return true;
}
}
if (MoveUp(maze, position, &newPosition)) {
if (FindPathThroghMaze(maze, newPosition)) {
return true;
}
}
if (MoveDown(maze, position, &newPosition)) {
if (FindPathThroghMaze(maze, newPosition)) {
return true;
}
}
if (MoveRight(maze, position, &newPosition)) {
if (FindPathThroughMaze(maze, newPosition)) {
return true;
}
}
return false;
}
再帰の利用に関するヒント
- 再帰を抜けるパスが含まれていることを確認する
- 安全カウンタを使って無限再帰を防ぐ
- 再帰をひとつの関数に限定する
void RecursiveMethod(int* safetyCounter) {
if (*safetyCounter > SAFETY_LIMIT) {
return;
}
(*safetyCounter)++;
//何らかの処理
recursiveMethod(safetyCounter);
}
まとめ
- 再帰を使う場合は、必ず他の手法を検討する
- 複数の関数にまたがる再帰はしない
- スタックオーバーフローに気をつける
所感など
再帰は頭が痛くなるので使いたくないし読みたくない!!
goto文
goto反対論
- 一般的には、goto文のないコードのほうが品質が良いとされる
- フォーマットが難しくなる
- コンパイラの最適化が無効になる
- コードが上から下に流れるという原則に反する
- javaをはじめとする現代の多くの言語では存在しない
goto賛成論
- gotoを見境なく利用するのではなく、特定の条件化で慎重に利用することを支持する意見が多い
- gotoを上手く配置できれば、コードの重複がなくなる
- gotoはリソースを割り当て、リソースを処理し、
リソースを解放する関数に役立つ - 場合によってはgotoを利用するとコードが速くなり、
サイズが小さくなることがある
エラー処理とgoto文
エラーが発生したらリソースを解放する(goto使用例)
//複数のファイルを削除する
void DeleteFiles(ErrorCode* error_code) {
*error_code = ErrorCode.FILE_STATUS_SUCCESS;
std::list<File> file_list;
//削除するファイルを取得
MakeDeleteFileList(&file_list);
std::iterator it = file_list.begin();
while (it != file_list.end()) {
//ファイルオープンチェック
if (!OpenFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_OPEN_ERROR;
goto FIN;
}
//ファイル上書きチェック
if (!OverwriteFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_OVERWRITE_ERROR;
goto FIN;
}
//ファイル削除チェック
if (!DeleteFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_DELETE_ERROR;
goto FIN;
}
++it;
}
FIN:
RemoveFileList(&file_list);
}
エラー処理とgoto文
gotoを避ける為にネストする例
//複数ファイルを削除する
void DeleteFiles(ErrorCode* error_code) {
error_code = ErrorCode.FILE_STATUS_SUCCESS;
std::list<File> file_list;
//削除するファイルを取得
MakeDeleteFileList(&file_list);
std::iterator it = file_list.begin();
while (it != file_list.end() && *error_code == ErrorCode.FILE_STATUS_SUCCESS) {
if (OpenFile(*it)) {
if (OverwriteFile(*it)) {
if (!DeleteFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_DELETE_ERROR;
}
} else {
*error_code = ErrorCode.FILE_STATUS_OVERWRITE_ERROR;
}
} else {
*error_code = ErrorCode.FILE_STATUS_OPEN_ERROR;
}
++it;
}
RemoveFileList(&file_list);
}
エラー処理とgoto文
gotoを避ける為に状態変数を利用する例
//複数ファイルを削除する
void DeleteFiles(ErrorCode* error_code) {
error_code = ErrorCode.FILE_STATUS_SUCCESS;
std::list<File> file_list;
//削除するファイルを取得
MakeDeleteFileList(&file_list);
std::iterator it = file_list.begin();
while (it != file_list.end() && *error_code == ErrorCode.FILE_STATUS_SUCCESS) {
if (!OpenFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_OPEN_ERROR;
}
if (*error_code == ErrorCode.FILE_STATUS_SUCCESS) {
if (!OverwriteFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_OVERWRITE_ERROR;
}
}
if (*error_code == ErrorCode.FILE_STATUS_SUCCESS) {
if (!DeleteFile(*it)) {
*error_code = ErrorCode.FILE_STATUS_DELETE_ERROR;
}
}
++it;
}
RemoveFileList(&file_list);
}
エラー処理とgoto文
try-finallyで書き直す例
//複数ファイルを削除する
void DeleteFiles(ErrorCode* error_code) {
error_code = ErrorCode.FILE_STATUS_SUCCESS;
std::list<File> file_list;
//削除するファイルを取得
MakeDeleteFileList(&file_list);
std::iterator it = file_list.begin();
try {
while (it != file_list.end()) {
OpenFile(*it);
OverwriteFile(*it);
DeleteFile(*it);
++it;
}
} finally {
RemoveFileList(&file_list);
}
}
4つの手法の比較
どれも一長一短・・・
- gotoを利用する方法は深いネストと無駄な評価をなくすが、
もちろんgoto文が入ってしまう (goto警察に絡まれる) - ネストしたif文はgotoをなくすものの、深いネストがロジックを複雑に見せる
- 状態変数を利用する方法はgotoと深いネストをなくすが、その文評価の数が増える
- try-finallyはgotoと深いネスト両方をなくすが、
すべての言語で利用できるわけではない
4つの手法の比較
- try-finallyが使えるなら使うのが最も単純
- それ以外の場合は、goto文やネストしたif文よりも状態変数を利用した方法がよい
- コードが読みやすくなる
- ロジックがうまくモデリングされる
- どの方法を選択するにせよ、プロジェクトのすべてのコードに一貫性を持たせることが大事
まとめ
- goto文の柔軟性につけこまないこと
- goto文を利用する場合は前方に進むものだけにする事
- goto文によってアクセスできないコードがない事
- goto文を他の方法で書き換える方法はいくつかあるので、
問題への妥当な解決策がgotoしかない場合は
その旨を明記した上で利用すること - 他のプログラマが提案するgoto文を利用しない方法に
心を開くこと
所感
使うかどうか悩むことが今後あるかすら怪しい
新しく書くコードにgotoは使わない
特殊な制御構造の展望
特殊な制御構造の展望
かつてgoto文は良い考えとみなされていた
- goto文を無制限に利用すること
- goto文のターゲットを動的に計算し、その場所にジャンプすること
- goto文を使って関数の途中から別の関数の途中にジャンプすること
- 行番号やラベルを使って関数を呼び出し、関数の途中で
実行を開始すること
特殊な制御構造の展望
ソフトウェア開発はプログラマがコードを使ってできることを
制限することによって大きく前進してきた
今後、ここで登場した制御構造は
ゴミ箱に捨てられる運命にあるのではないか
まとめ
まとめ
- 複数のreturn文は関数の可読性と保守性を向上させ、
深くネストしたロジックを避けることに役立つ
- 再帰は、問題の範囲が狭い場合の的確な解決策になりうる但し、慎重に使うこと
- 可読性と保守性の良いコードを書くために、goto文が最善の方法になるケースがいくつかある
しかし、そのようなケースはまれである
gotoはあくまで最後の手段として利用すること
資料作りが下手でもそれっぽく見せれる気がする
Copy of CODE COMPLETE第6回レビュー
By Samil Vargas
Copy of CODE COMPLETE第6回レビュー
特殊な制御構造
- 708