分岐の書き方
解決したいこと
皆さんの意見を知りたいです
自分はif文を書くとき、無駄なインデントを嫌って ! と continue; break; return; を多用した文を書きます。
例えばナンプレのプログラムを作るときに、下記のように縦横のラインと3かけ3のブロックで同じ数字がないか走査する関数を作ったとして
private bool horizontalScan ( int x, int y )
private bool verticalScan( int x, int y )
private bool blockScan( int x, int y )
// 同じ数字が見つかればfalseを返す
この場合、同じ数字が見つからなかったら次の処理をを行う操作は
if ( horizontalScan(x, y) ) {
if ( verticalScan(x, y) ) {
if ( blockScan(x, y) ) {
// 次の処理
}
}
}
とするより
if ( !horizontalScan(x, y) ) { continue; // または return; など }
if ( !verticalScan(x, y) ) { continue; // または return; など }
if ( !blockScan(x, y) ) { continue; // または return; など }
// 次の処理
にしたほうがスマートだと感じています。
まぁそもそも
private bool scan ( int x, int y ) {
if ( horizontalScan(x, y) ) {
if ( verticalScan(x, y) ) {
if ( blockScan(x, y) ) {
return true;
}
}
}
return false;
}
public void Func ( int x, int y ) {
if ( scan(x, y) ) {
// 次の処理
}
}
と関数にしてしまえばいい話ですが
上の例は極端な例なので場合によるとは思います。
ただ自分はループ以外であまりインデントを使うと可読性が失われると思っているのでこういう書き方をよくしてます。
皆さんはどう思いますか?
また、入れ子にしたほうが良いと思う意見があれば聞きたいです。勉強中の身なので
この問題は真剣に追うとネストだけに深いので,詳しくはearly returnについて調べてみてください.
無闇にearly returnでリファクタリングすると(初学者の方は特に)意図せずバグを生じるので気をつけてください.
これ可読性だけじゃなくて関数に役割を持たせすぎないことにも関わってくる重要な概念です.
大抵のコーディング規約では改行するように決められている。
個人の好みではなく規約で決まっていること。
1行で書いてスマートに見えるのは最初だけ。
if ( !horizontalScan(x, y) ) { continue; }
一度書いて終わりではなくコードは追加されていく。この時の差分が無駄に大きくなるから「中身が1行しかなくても最初から改行しろ」と決まっている。1行で書きたがるのはコードを修正する発想のない初心者が最初に陥る罠なので早めに考えを変えたほうがいい。
if ( !horizontalScan(x, y) ) {
// 追加コード
continue;
}
これは改行ではなく「早期リターン」の話。
if ( !horizontalScan(x, y) ) { continue; // または return; など }
if ( !verticalScan(x, y) ) { continue; // または return; など }
if ( !blockScan(x, y) ) { continue; // または return; など }
私は「一度に把握すべき情報は少ないほう良い」と考えているので次のような方針で書いています。
- ネストが深いよりは、浅いほうが良い
- 先まで読まないと結果がわからない状態より、早期リターンですぐに結果がわかるほうが良い
入れ子にしたほうが良い具体的なケースは思いつかなかったのですが、例えばロジックの元になっている情報(仕様やマニュアル等)が入れ子のような形で表現されている場合はそれに合わせた表現が適切かもしれません。元になっている情報と表現を揃えることで対応付けを行う、という考えです。
また、これらは可読性を重視した考え方なので、パフォーマンスなどの他の要素を優先する場合にあえて逆の方針を選ぶことはあると思います。
自分も深いネストは嫌います。
なお、if文がネストしていて、それぞれのelseがないならば、それは単にand条件なので、&&でつなぎます。
処理の分岐でなく、設定値の振り分けなら、三項演算子やKotlinならif式/when式、Swiftならif式/switch式を使います。
各言語によって「コーディング規約」となる基準がありますから基本はそれに従うのも良いでしょう。しかし可読性が下がるなど様々な理由があるなら無視してあえて自分のスタイルで書くことはあります。
言語によっては「コーディング規約」にとりつかれすぎるのも良くないと書かれていたりします。
例えばPythonは的を得ていると思います。
https://pep8-ja.readthedocs.io/ja/latest/#id3
スマートだと感じています。
「スマート」の解釈が私と違います。
コードをパッと見て分かりやすく、かつコード量が少なくできるものを「スマート」と呼んでいます。
言語によってはcontinue
が使えなかったり、ブロック内のコードが増えてしまうならまだ私は入れ子(ネスト)が深い方を選びます(ただ深すぎるネストを良いとは思っていません)。
もちろんif (exp) { }
のように一行で書くやり方もします。
しかしこの場合、ブロック内のコードが意味のある物に限ります。
例えば変数を代入{ a = 1; }
するなど。
continue
やreturn
だけの為には使いません。
ブロック内のコードとしてあまり意味を成さず、入れ子にすればわざわざcontinue
を書かずそれだけで一つ遠回りなコードが生成される訳なのでその点がスマートでは無いと感じています。
解釈の違いは当然ありますから、参考までに。
フローを人に説明するのだと考えたときに,以下のどちらの言い方をした方がわかりやすいと思われるか? みたいな点で決めれば良いと思う.
質問文内の例だと
早期に return する書き方のイメージは,
-
!horizontalScan(x,y)
であればfalse
-
!VerticalScan(x,y)
であればfalse
-
!blockScan(x,y)
であればfalse
- それ以外であれば
true
みたいな.
対して,3重の if
を用いた書き方は
-
horizontalScan(x,y)
であって,且つVerticalScan(x,y)
であって,且つblockScan(x,y)
である場合にはtrue
- そうでなければ
false
みたいな.
言うほどの差が無いと思うのであれば,「ネストが深いと単純に見辛い」みたいな別の要素が判断基準として強くなる…かな.
質問のコードを見ると、
横、縦、ブロックを順にチェックしていって、いずれかに同じ数字がある場合は「次の処理」は行わない
というのがやりたいことで、その条件をチェックするのに if 文のネストを深くしたくないということと理解してます。
その理解で正しければ、以下のようにしてはいかがですか?
if (horizontalScan(x, y) && // 横に同じ数字があれば false
verticalScan(x, y) && // 縦に同じ数字があれば false
blockScan(x, y) ) // ブロックに同じ数字があれば false
{
// 次の処理
}
}
if 文の ( ) 内の一番左の horizontalScan から一番右の blockScan まで順に評価されていきますが、&& でつないでいるので途中でどれかが false になればそこで評価は終了して、if 文の ( ) 内は false になります (短絡評価)。
条件付き論理 AND 演算子 &&
https://learn.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/boolean-logical-operators#conditional-logical-and-operator-
質問の 3 つ目のコードブロックの continue とか return を使う方法は自分的にはスマートとは思えません。他の方がコメントされてますが、バグのもとにもなりそうですし。
さらに言わせてもらえると、メソッドの名前も
// 同じ数字が見つかればfalseを返す
が、メソッド名を見ただけで分かるように考え直した方が良さそうに思います。