
複雑なコードは悪いコードか 184
ストーリー by hylom
他人が見て分からないコードは悪いコードです 部門より
他人が見て分からないコードは悪いコードです 部門より
eggy 曰く、
プログラミングにおいて簡潔に書かれているコードが良いコードであり、一方で複雑なコードを書いたプログラマーには技量が無く、複雑なコードはどんな馬鹿でも書けると思われがちである。だがDr. Dobb'sの編集長Andrew Binstock氏は、こうした二分法的な考え方はいかがなものかと批判している(本家/.、Dr. Dobb's記事)。
Binstock氏は、複雑なコードを書くのは難しく、どんな馬鹿でも書けるものではない、そして複雑な問題には複雑なコードが必要なのだと述べている。複雑なコードを、下手に書かれたコードと同一視するのは間違いであるとしている。
Binstock氏の定義によれば、簡潔さとは理性的で中立的であることなのだという。つまり、簡潔に書かれたコードとは、潜んでいる複雑さを説明する限りにおいては複雑にもなりえるため、簡潔に書かれたコードが極めて複雑である場合もあるのだ。良いか、悪いか、といった二分法的な考え方はプログラミングには内在しないとのこと。
杓子定規な評価基準 (スコア:3, 興味深い)
「一画面に収まらない関数はダメ」とか、「ネストが深ければダメ」とか、これって処理内容を無視した評価基準ではないでしょうか。
処理内容によっては、そういったやり方がベストな場合もあると思います。
オブジェクト指向にしてもそうです。
アクセッサを使わずに、変数をpublicで公開しても良い場合もあると思います。
変数のチェックをしない場合のアクセッサには、どれほどの意味があるのでしょうか?
多態性を使わずにswitchで切り替えたほうがシンプルな場合もあります。
staticなpublic関数?大いに結構じゃないですか、シンプルで最もオーバーヘッドが小さな実装法だと思います。
画一的な基準で評価するのは工学的アプローチと言えますが、ソフトウェアは芸術的・職人的な側面もありますから。
Re:杓子定規な評価基準 (スコア:2)
結果、殆どの関数が50行で足りない物が結構出て来たなぁ
Re:杓子定規な評価基準 (スコア:1)
一画面というのは変な基準だと思いますが
ネストが深いのは改善すべきだと私の心のメトリックスは言ってます。
ネストを浅くする何らかのロジック変更が効率がよくわかりやすくなることが多いです。
経験上ね。
Re:杓子定規な評価基準 (スコア:1)
「一画面に収まる」は前世紀ではそれなりに意味のある基準でした。パンチカードでプログラムを入力していた頃は物理的に80桁に収めねばなりませんでしたし、コーディングやメンテを粗末なキャラクタ端末とviで行なっていた頃は80桁を超えて行が折り返してしまうとまともに編集出来ない場合がありました。今となっては何の意味もないわけですが。
Re:杓子定規な評価基準 (スコア:1)
ほかはともかく、アクセッサに関しては同意できないなぁ……。
アクセッサを経由しないメンバ変数へのアクセスを許すと、仕様変更に弱くなりますから。
不具合の追及も困難ですし。
Re:杓子定規な評価基準 (スコア:1)
「こんな事もあろうかと」と言いたいがために、あるかどうかわからない将来の拡張に備えて、余分なコードを追加するというのはむやみにコードが間延びして、良くないです。拡張が必要になった時に、ごっそり書き換えれば済む話です。
だから、パラメータを右から左に渡すだけのアクセッサーには反対です。(逆に、値チェック以上の重たい処理をアクセッサーに入れるのも別の理由でダメ)
ただし、不特定多数に公開されるライブラリーにおいてはこの限りではなく、予防的にアクセッサーにするのはありだと思います。
そもそも、オブジェクト指向技術は基本的にライブラリーを作る場合に必要な技術であり、普通のプログラムでの使用は、弊害のほうが大きのではないかと思います。
Re:杓子定規な評価基準 (スコア:1)
>変数のチェックをしない場合のアクセッサには、どれほどの意味があるのでしょうか?
ブレークポイント設定できる。
え?ウォッチ式でいいじゃんって?
屍体メモ [windy.cx]
Re:杓子定規な評価基準 (スコア:1)
なんでそんなの読むのがデフォなんだ?
the.ACount
Re:杓子定規な評価基準 (スコア:1)
なんで威張ってると思うんだ?
the.ACount
「他の条件が同じならば」 (スコア:2)
「他の条件が同じならば」が抜けてるから、そういうズレた議論になるんだと思う。
単純なものを実現するコードより、複雑なものを実現するコードの方が複雑になるのは当たり前。
しかし「他の条件が同じならば」「同じことを実現するには」簡潔でシンプルな方が良い。
世の中には簡単なことを実現するにも、無駄に長くて複雑でデバッグも修正も出来ないコードを
書く人がいるんだよ。そういうコードは、スパゲッティコードとか呼ばれたりするんだけどね。
Re:「他の条件が同じならば」 (スコア:2, 興味深い)
while(*src++ = *dst++);
みたいなコードは賛否わかれますね。
条件式の中に本処理とイテレーションを突っ込みまくってループ本体は空、
こういうコードは一般的には汚く複雑であるとみなされますが、
よく知られた「慣用句」ならば簡潔でよいとされることも多々あります。
言語の構文を越えた、「実装の語彙」の問題のように思います。
Re:「他の条件が同じならば」 (スコア:1)
末尾の文字をコピーした時点で,評価値が 0 (偽) になってループは終了する.
Re:「他の条件が同じならば」 (スコア:3)
いや、たぶん親コメントはそういうことを突っ込んでいるわけじゃなくて…。それはそうと、src と dst って逆じゃないです?>#2413752
確実にこの前提が守られるとしても、領域が重なっていて *dst の末尾を *src が上書きしてしまうケースがまずくないですか。
1年後に自分が書いたコードを見て (スコア:2, おもしろおかしい)
理解できない・・・と怒り出す人ですね
Re:1年後に自分が書いたコードを見て (スコア:3)
#Insanely great!
#をかのゆ
Re:1年後に自分が書いたコードを見て (スコア:3, おもしろおかしい)
このコードを書いたのは誰だあっ!!
と騒いで、みんなから指を指されるんですねわかります。
Re:1年後に自分が書いたコードを見て (スコア:3)
#Insanely great!
#をかのゆ
Re:1年後に自分が書いたコードを見て (スコア:2)
ばかもーん! そいつがルパンだ追えー!
複雑なコードとは? (スコア:2)
int FuncA(int);
int FuncB(int);
int FuncC(int);
...
int (*Func[])(int) = {FuncA, FuncB, FuncC, ...};
...
rc = Func[State](Param);
なんてコードは、新入から「このコードは複雑で何をしてるか分からない。switchの方が分かり易くて簡単だ。」なんて言われます。
256個のcaseが並んだswitchには驚かされますね。(関数と変数は異なるもので、関数の配列は考えられないといった思考のようです。)
どんなコードが「複雑なコード」に思えるかは人によって違うので、「複雑なコード=悪いコード」と固定することは出来ませんね。
# switchでは実行速度にばらつきが出て修正となりました。
# この件では、私「複雑なコード=悪いコード」、新人「複雑なコード≠悪いコード」だったと。
Re:複雑なコードとは? (スコア:2)
実際に経験したもので言えば、シェルスクリプトで先頭行に
と書いてあるのに、bashの機能を使うことを禁止された、なんてことがあった。曰く、Ryo.F以外には理解できないコードになるから、だと。
かぐぐれば済む話なんだけどな。
Re:添削 (スコア:1)
実際にはそれらしい名前をつけてますし日本語コメントや全体の資料も付けてます。さらに説明もするのですが、関数と変数を分けてしか考えられない新人(自称C言語経験者)には「関数のポインタ」の存在が分からないため複雑で読めないコードになるようです。
私の周りだけでこれまで数人いましたので、全体的には結構な人数居るのでは?
他にも再帰処理とかを読ませて、「理解できない人=先がない人」「理解できた(しようとする)人=教育する価値がある人」と考えるようにしています。(私の基準です。)
# 以前、ググって見つけたコードを繋ぎ合わせるしかできない新人には降参でした。
# 言語仕様すら学ぼうとしないので、「関数が呼べないので見て欲しい」と頻繁に聞いてきた。
# 完成したコードにはsample...,hoge...なんて名前が大量にあり難読なコードだった。
Re:添削 (スコア:1)
switch case で書いて、己のポカでバグを仕込むようになってハマると、関数ポインタがよいと理解し始めるとおもうな。
まぁ、近頃の風潮では「失敗させて学ばせて」なんて悠長なことできないんですけどねぇ・・・。
Re:また新人叩きかよ (スコア:1)
新人叩きではなく私の経験と考えを書いただけです。
一部の新人のことですし、当の新人には教育を行った結果、自らの不向きを理解させた上で職種を替えてしっかり働いています。
>お前も大したレベルじゃないんだろ。
はい。大したレベルではありません。
独学の組み込み系なので、かなり古く低レベルだと思います。
>自己顕示欲だけはすごいよな。
?
なぜそう取られたのでしょうか?
>君の一方方向からでた情報になんの信頼性もないからw
当然です。信頼性なんて求められても困ります。
信頼性なんて求めてたら、このようなサイトは成り立ちませんよ。
信頼できないなら違う意見を書き込めばいいだけです。そうすることで議論が活発になり良いサイトになると思います。
小さな企業では新人の向き不向きを早期に判断し、適切に配属しないとやっていけません。
育つ見込みがない者を、本人が望むからと言うだけで教育し続ける余裕などありません。
最初にコードを書ける書けないは重要ではないのです。書けなければ学べばいいのですから。学んだ結果、問題の少ないコードを書ければ良いのです。
しかし、「学ぼうとしない」、「他の意見を受け入れない」、「考えようとしない」人にコードを書かせることは、会社にとっても本人にとってもマイナスでしかないと考えています。問題が多いコードをいつまでも書き続けるなら、使えない人材と判断するしかありません。業務としてやる以上、仕方のないことです。
複雑でもいいんだよ (スコア:2)
見易ければ
問題の複雑さとコードの簡潔さが一致しない例 (スコア:2)
問題の複雑さとコードの簡潔さが一致しない例として、
malloc があったのを思い出しました。
K&R の実装では、60行程度で実装してますが、
この K&R の実装に性能面で勝つためには、とても複雑な実装になります。
例: glibc malloc 実装の解説スライド [slideshare.net]
これは、「簡潔なコードに隠されている問題の複雑さ」という K&R の実装と、
「複雑な問題には複雑なコードが必要」という glibc malloc の実装の一例が
対比しやすい例だと思います。
先日、元同僚とあった時のお話 (スコア:2)
その元同僚は、前の職場で数少ない「価値観の合う友人」であり、「感覚が近い」人間でした。
そんな彼が、「今なんでカミングアウト」と言って教えてくれたのが、
僕が色々あって休職している間、アプリの改修依頼をしてくれたのが彼だったという事でした。
そんな彼曰く
「ただ単にコピペを繰り返しただけですよw」
でもそれって、「そのソースを理解出来てる」からこそ出来るんだよね。
話を聞いていけばいくほど、「僕の意図したロジック」をちゃんと理解していた事が分かってきて。
めっちゃ嬉しかったなぁ。
僕が復職した時には、彼は既に他の職場に転職していたので、当時は全く知らなくて。
復職した時に聞いていたのは、「他の部の人達が、めっちゃ大変だった」って話くらいで。
当時は話を深く聞く事はなかったけど、やっぱり理解できなかったらしい。
やっぱり、「自分に合うコード」=「綺麗(正解)なコード」って訳じゃないんだなって思いました。
オリンピックの開催年数を表示する例題 (スコア:1)
なんかで読んだ例題で「近代オリンピックの開催年数をすべて表示する」というのがあって、与えられる仕様が以下のとおりー。
・夏季オリンピックは1896年から4年毎で開催
・冬季オリンピックは1924年から4年毎で開催。ただし、1992年の次は1994年から4年毎に開催
・1916年、1940年、1944年は戦争のため開催なし
人によって実装はまちまちだけど、これを誰が見ても分かりやすくメンテナンスしやすいコードを書くにはちょっと経験がいるかも。
模範解答は膝を打った。
まぁ実際の開発の仕事ではもっと複雑だろうけど。
Re:オリンピックの開催年数を表示する例題 (スコア:2, すばらしい洞察)
"http://ja.wikipedia.org/wiki/近代オリンピック"
Re:オリンピックの開催年数を表示する例題 (スコア:1)
Re:オリンピックの開催年数を表示する例題 (スコア:1)
「ただし、1916年、1940年、1944年は除外する」
とか表示しても、それは正解にはならないのですよね、きっと。
¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
Re:オリンピックの開催年数を表示する例題 (スコア:1)
「近代オリンピックの開催年数をすべて表示する」のが命題なんだから正解ではない。
というか、一般には、「近代オリンピックの開催年
数のみをすべて表示する」と解釈すべきだろうね。「のみ」でなくていいなら、すべての整数を枚挙する無限ループを書けばいいってことに。
Re:オリンピックの開催年数を表示する例題 (スコア:2)
真っ先に、この方法を思いついたよ:)
大抵は表示するコストが高いから、
「近代オリンピックの開催年数のみをすべて表示する」
ように高速化を狙って、複雑になる、と。
Re:オリンピックの開催年数を表示する例題 (スコア:1)
開催年を単純に計算して表示するプログラムAと
入力から、1916 と 1940 と 1944 以外のデータだけ出力するプログラムBを
パイプでつなぐ。
とか、
開催年を単純に計算して表示したあと、画面(あるのか?)を走査して、1916と1940と1944を見つけたら消す。
ああ、「なかったことにする」処理は難しい。
¶「だますのなら、最後までだまさなきゃね」/ 罵声に包まれて、君はほほえむ。
Re:オリンピックの開催年数を表示する例題 (スコア:1)
> 誰が見ても分かりやすくメンテナンスしやすいコード
なので、
以下の考え方が良いような気がします。後々、使い回しも出来そうですし。
> 下手に無駄なく生成するアルゴリズムよりも、
> 全ての年について条件に当てはまるか否か判定・抽出するアルゴリズムの方が、
> 一見バカっぽく見えても、仕様をコードに落としこむ際のエラーは少なそうに思える。
したがって、以下の様な何の変哲も無いプログラムになります。
効率が犠牲になりますが、この程度なら、大した問題にならないでしょう。
# ラムダ式を使えば、もう少しエレガントになりますか?
static void Main()
{
var orympicYears = new List<Int32>();
for( Int32 year=1896; year<=2013; year++ )
{
if ( JudgeIsOrympicYear( year ) )
{
orympicYears.Add( year );
}
}
}
Boolean JudgeIsOrympicYear( Int32 year )
{
// 1896年未満はそもそもオリンピックは存在しないので除外。
if ( year < 1896 ){
return false; // No Orympic Year ( Not Exist )
}
// 戦争による非開催年は除外。
switch ( year ) {
case 1916:
case 1940:
case 1944:
return false; // No Orympic Year ( By War )
}
// 夏季は1896年より4年おきの開催。
{
Int32 tmp = year - 1896;
if ( ( tmp >= 0 ) && ( tmp % 4 == 0 ) ){
return true; // Orympic Year ( Summer )
}
}
// 冬季は、1992年以前は、1924年より4年おきの開催。
// 以後は、1994年より4年おきの開催。
{
Int32 tmp = year - ( year <= 1992 ) ? 1924 : 1994;
if ( ( tmp >= 0 ) && ( tmp % 4 == 0 ) ){
return true; // Orympic Year ( Winter )
}
}
// 夏季も冬季も非開催ならば、除外。
return false; // No Orympic Year
}
天才・秀才・凡人 (スコア:1)
天才が書いたコードは、複雑で読みにくいが良くきれいに動く。
秀才が書いたコードは、きれいで読みやすく、ちゃんと動く。
凡人が書いたコードは、ぐちゃぐちゃで読みにくいし、しばしば誤動作する。
天才にはならなくてもいいけど、秀才レベルのコーディングはしたいものだ。
この記事は伸びない (スコア:0)
いきなり結論を出しちゃダメだ。
Re:ソート (スコア:1)
まず思い付くのがクイックソートで、バブルソートなんて使われたら解読に苦労するわい。
the.ACount
Re:ソート (スコア:1)
細かい話をすれば、数が十分少ないときは、クイックソートよりバブルソートの方が早いので、数が少ないときはバブルソート、それ以外はクイックソート、なんてことをやるみたいですね。
もっとも、凡百が書いたソートより、ライブラリにあるものの方が、実装速度上も実効速度上も速いでしょう。選択の余地はないと思います。
Re:必要条件なんじゃないの? (スコア:1)
単純でも悪いコードはあるだろ。
で閏年を判定するとかさ。
Re:必要条件なんじゃないの? (スコア:1)
2100年まで保守する気も1900年以前に遡る気もないなら、別にいいんじゃない?
2000年問題憶えてますか。まだ2038年問題も控えてますね。
2100年まで運用(保守ではない)を続けなければ良い、ってことはないですね。運用中に1900年以前(の過去)を遡る可能性を考えるのと同様、運用中に2100年以降(の未来)を扱う可能性があるかを考えるべきです。
今どきのコンパイラなら& 3にしてくれるし。
「& 3」って何ですか?
Re:必要条件なんじゃないの? (スコア:1)
私の理解はこの手のよくある言い回しに対する世間一般的な解釈と変わりません
そう思い込んでいるだけでは? 説明してみれば明らかになりますよ。さあ、どうぞ。
それと、「悪いコードは複雑である」とは限らないことの実例にもなってるかを、理由も合わせてお答えください。
Re:必要条件なんじゃないの? (スコア:1)
件の例は、良いコードの例なので
悪いコードも例示しましたが。
要するに、まともな反論はできないということですね。
Re:必要条件なんじゃないの? (スコア:1)
そちらは悪いコードではなく、正しくないコードなので、悪いコードは複雑の例にはなりません。
どちらもソートプログラムとして正しいですよ。
プログラミングできないのなら、そう言ってください。素人にも解るように説明しますから。
いずれにせよ、まともに反論できないということですね。
Re:必要条件なんじゃないの? (スコア:1)
所詮は「立場が違う」以外の回答を得られそうにないあなたに対し
相手との立場の違いを認められないのですか?
それでは話になりませんね。
私がわざわざ一般教養レベルの説明をして差し上げる意義など感じません。
私もその意義を認められません。それでぜんぜん構いませんよ。さっさと別の相手を探してください。
あなたがRyo.F大好きなのは、良く解りました(笑)。
Re:必要条件なんじゃないの? (スコア:1)
私が言った「正しくないコード」
既に言及した通り、立場の違いでしかないね。
閾値なしにクイックソートするだけのプログラムですがこれは悪いというほどの物ではありません。
既に言及した通り、「良い・悪い」の測り方による。君はRyo.Fが提示した枠組から一歩も踏み出していない。まったくもって君にはがっかりだよ。
反論するならば、「良い・悪い」の測り方を明示したまえ。ほとんどの場合、反例は作れるだろう。
Re:必要条件なんじゃないの? (スコア:1)
そこに込められた「行間」を常識的に補足すれば
論理に行き詰まると常識を持ち出すやつが多いね。困ったもんだ。
複雑なコードが悪いコードであるとは限らないが、
(俗に)悪いコード(として槍玉に挙げられる類いのコード)は
(大概が)複雑なコードである
そこまで行間を読んじゃうのは、妄想に近いよ(笑)。
もしそうなら、「複雑なコードが悪いコードであるとは限らないし、悪いコードが複雑なコードであるとは限らない」だよね。前半で例外を強調しているなら、後半の断定は、例外を含まないと読むことも十分ありうるね。
「複雑だけれど正しいコード」を今更いくら例示しても
正しくて単純だけど悪いコードと、正しくて複雑だけど良いコードを例示してるのが読めない? そりゃまた困ったもんだ。
Re:必要条件なんじゃないの? (スコア:1)
各人の「良い・悪い」の測り方や、「立場の違い」にて逃れようとしているようですが、もともとその定義は原典 [drdobbs.com]にて提示された定義に則ってるわけ
原典を持ち出せば何かが誤魔化せると思っているようですが、それは間違いです。
原典では、「良い・悪い」の測り方は完全には定義されていません。
一方、「単純・複雑」の測り方は定義されています。コードの表面上の複雑さだけで測るのではなく、元も問題の持つ複雑さを考慮して測るべき、としていますね。
その定義に基づいて、私の例示した二つの例 [srad.jp]を考えてみましょう。コードの表面上の複雑さは、前者が単純で、後者が複雑。一方、前者・後者とも同じ問題(ソート)を扱っています。従って、元の問題の持つ複雑さは同じ。したがって、原典の定義に基づいても、前者が単純で、後者が複雑と言えます。
一方、実行速度に差がありますので、前者が悪く、後者が良い。
つまり、前者が単純で悪く、後者が複雑で良い、となりますね。
そこに個人的な解釈などナンセンスですね。
そうですか? そんなことはないと思うけどなあ。
しかし、もしそうだったら、私と同じ結論になることになりますね。
Re:長いコード≠複雑なコード,複雑なコード≠無駄に長いコード,短いコード≠簡潔なコード≠解りやすい (スコア:2)
SQL廚ですけど、A = null かB = nullの場合にelseに行くと思われ。
null = null はfalseって忘れやすいんですよね(汗
Re:長いコード≠複雑なコード,複雑なコード≠無駄に長いコード,短いコード≠簡潔なコード≠解りやすい (スコア:1)
Bの値が0ならelse節いくんじゃ?
Re:見なくてもいいところは複雑でもかまわない (スコア:2)
あれで一番びっくりしたのは、2chに書いた匿名コメントと/.J日記をヒモづけられたことですよ。キーもないのに、一体どんな複雑なアルゴリズムを使って結合(?)したのか!?