伏線回収です。
TL;DR
- 当該記事は総論賛成だけれど、「古い」「ダサい」で物事は変えられない
- 最近の環境の変化、コードを書いた人が持っている暗黙の前提なども考えないといけない
- 単純にコードを変えるだけでなく、それに合わせて環境やプロセスを整える必要がある。そこはみんなそれぞれ頑張れ。
まずは分類
9個の「古い書き方」が挙げられていますが、実はいくつかに分類できるかと思います。私が分けるなら以下のような感じです(いくつかにまたがっているものもあると思いますが、私なりにエイやで分けました)
-
エディタやIDEの進化により不要/必要性が低くなったもの
- 変数名が雑
- 無駄な省略
- ハンガリアン記法
- ヨーダ記法
-
古い言語の常識を新しい言語でも使ってしまうもの
- 必要以上に広いスコープで変数宣言する
- 言語として用意されている機能を使わない
- goto
-
バージョン管理システムを使いこなせていないもの
- 変更するときに昔のコードをコメントアウトして残す
-
コードの可読性の問題
- 無駄に()つけている
エディタやIDEの進化により不要/必要性が低くなったもの
まず、モダンなIDE(VSCodeやJetbrains)や、Vim/Emacs向けのLanguage Serverの普及など、エディタの普及により必要性が低くなってしまったものです。
雑な変数名や無駄な省略がなぜ発生するのか、というのを考えると、勿論名前をつけるのが面倒くさい、というのもあると思うのですが、それ以上に(特にベテランの方の)反応として返ってくるのは「長い文字を打つのが非効率」というのがあります。
そう、かれらは偉大なるIntellisense(コード補完)に不慣れ、というよりほとんど利益を受けてきていないんですよね。仮に長い名前をつけてしまってしまっても、きちんとIDEやエディタを設定していれば3文字から4文字も打てば候補がほぼ絞り込めるはずです1。
同様にハンガリアン(正確に言うとシステムハンガリアン)記法も必要性は少なくなっています。変数名にカーソルを持っていけば型を表示してくれますし、そもそも安全でない代入は警告が発生することがほとんどです2。
ただハンガリアンについては、本来の意味である「プログラム上の意味を表すハンガリアン」や、あるいはアクセス修飾子もハンガリアン的に書くことは一般的とは思いますので、個人的にはシステムハンガリアンはもういらないよ、でいいのかなとは思います。
ヨーダ記法はWikipediaの記述にもあるとおりで、代入演算子と等価演算子を混同しないために使われていることは未だに多いですね。
public sample(price : number) : void{
if (0 = price) { // コンパイルエラーになって嬉しい!!
}
}
ただ、そもそもこれも現在のLintツールやIDEの警告で十分にカバーできます。
これらのルールを削除することには異論はありませんが、その代わりチームのルールとして、モダンなエディタを利用し、コミット時などのLintチェックを仕組みとして取り入れる、というところまでセットで実行することが重要だと思います。
古い言語の常識を新しい言語でも使ってしまうもの
次のカテゴリとしては、古い言語の常識を新しい言語でも使ってしまうことですね。ここでいう「古い言語の常識」とは、現在現役の言語の過去バージョンも含みます(Java7以前、C# 3.0以前、C++11以前、などなど)
さて、まず変数のスコープについて。これは元記事のコメント欄でもあるように、C99以前のC言語では必ず最初に変数宣言をつけないといけないという話があったりしました。しかしこれが現在のC/C++で変数宣言を冒頭に持ってくる理由にならず、最新の規格に従って書きましょうというのが大前提になるでしょう。
言語としての機能についてもそのとおり。特にC#におけるLinq、Javaにおけるstream3、あるいは全言語共通的にラムダ式(あるいは関数オブジェクト)については、結構障壁があるようには思います。
この辺は認知負荷の問題だと思っていて、何度も何度も書いていればなれてくるもんだな、というのは感覚的にはあります。基本的には、コードを短縮できる場合が多いので、私がおすすめする場合には、
- コードがまず短くできる
- 意図を示すコードとして書きやすい
- 並列化可能性など最終的なパフォーマンスとして有利な場合がある
というくらいの順番で必要性を説いていきます4
あと goto
ですが、実はよくあるパターンは「例外を知らない/使いこなせていない」です。
シェルとかだと goto
使わざるを得ないことあるので、その辺書いたことあるひとはピンとくると思うんですが、 goto
ってエラー処理的なものを書くのによく使うんですよね。特にfinally句として非常に使い勝手がいいです。
んで、例外がある言語に関しては、そもそも例外で同様のことができることを知らずにgoto
使っていることがあるんじゃないかと思います。というわけで、例外をうまく使いましょう5
バージョン管理システムを使いこなせていないもの
古いコードコメントアウト問題ですね。
これ、実は「git使え」だけの問題だけじゃないよなと思っていて、この抵抗にはいくつかの理由があると思います。
まずは、gitに入れたとして、どうやったら古いコードが復帰できるのかがわからない、というパターン。
この対処法は結構簡単で、Git機能組み込みのエディタで持って、git blame
を見せると大体は納得してくれます。結局、どうやってこのポイントに戻ったらいいの?というのがイメージできないというのが大きいですね。
git blame
に限らず、タグを打ったり、ブランチを切ったり、stashしたり、みたいなGitのリテラシーを身に着けてもらって、コードコメントアウトでやりたいことが代替できる、ということがわかってもらえればいいのかな、と思います。
…じつはもう一歩踏み込むと、そもそもなぜ「過去のコードをコメントで残す」という行為が必要なのか、ということに行き着きます。いくつかあると思うのですが、ざっと考えられる理由を考えると
- 版管理のため
→ これはGitで完全に代替可能 - 消したコードを復帰させたい場面がある(デグレード時など)
→ これもGitを使えば復帰可能だが、そもそもそんなに恐る恐るコードを消すのはなぜ?テスト足りてないんじゃない? - 「コードのライン生産量」がメトリクスになってしまっているので、行数を減らす変更をしたくない
→ そもそもそんなLOCでの生産性は測らないよ、と品質やベロシティの計測基準の共通理解を作る
こんな感じのところまで考えたほうがいいのかなと。
コードの可読性問題
演算子の順序が自明な場合、()は省略しよう。もし可読性のことを考えて()をつけるなら、むしろ変数や関数に切り出そう、というのが元記事の主張で、これには反論の余地なく、同意します。
同意しますが、実はカッコに関して言えば、もし自明の場合に省略していないのならば、なんでifの{}
を省略しないの?というのは、一つの疑問として上がります。
元記事のサンプルコードを引用すると
public getPrice(fuga : boolean, piyo : boolean) : number {
let price = 0;
// ここでitem宣言するとif文の外でも使われるのかと思われてしまう。
const item = this.getItem();
if(fuga){
price += 100;
if (piyo){
// このif文の中でしかitem使ってないよね?この中で宣言しようね?
price += item.Price; //追記コメント: ここ1センテンスだからそもそも中括弧いらないよね?
}
}
return price;
}
上記なんかですね。例えば、私だったら以下のように書くかもしれません。
public getPrice(fuga : boolean, piyo : boolean) : number {
if(!fuga) return 0 //そもそも早期リターンできる
const basePrice = 100
// ここでitem宣言するとif文の外でも使われるのかと思われてしまう。
const item = this.getItem()
if(piyo) return basePrice + item.price //ここはカッコ入れてもいいかもね
return basePrice
}
if
のステートメント反転で、早期リターンしちゃうでしょう。両方とも1ステートメントなので文法上のカッコは不要です。シンプルなロジックの場合には、そもそも if
句の前で改行を入れないことで、1行に収めたほうが、個人期には読みやすいと思います。
…とまぁ何を言いたいかと言うと、可読性というのはコンテキストにより変化する要素が大きいのです。
なので、Lintなどのツールによるチェックと、何より相互レビューやペアプロ/モブプロを通して、チームとしての「可読性」の意識を合わせていくほうが何よりも大切です。
終わりに
というわけでダラダラ書きましたが、言いたいことは最初と同じで、
- 「古い」「読みにくい」などの理由だけでのルール押しつけは誰の得にもならない
- 相手のコンテキストを理解して、なぜそんなコードを書いているのかを理解する事が必要
- コーディングルール単体ではなく、IDEなどの環境、Gitなどのプロセス、Lintなどのツールを包括的に設定することが必要
最後に一言だけ。
理想に燃えている若者を、コメント欄でそれこそ嫌味のような言い回しをして、知識不足をあげつらうのが本当に正しい姿勢なのですかね?私も苦言めいたことは書いちゃったとは思いますが、ちゃんと記事という形で、自分の主張を自分の場で書くことにしました。このへんが最低限の先達としてのやり方なのではないかと思います。見ていて正直気持ちのいいものではなかった、というのを自分の感想として書き残しておきます。
-
もしそれで絞り込めないんだとすると、その変数名は冗長すぎるか、あまりにもスコープを大きく取りすぎているかだと思いますが、それはちょっと別の話とさせてください ↩
-
Cとかはもしかすると安全でない場合があるかもしれません。CppLintなどでなんとかなりそうな気もするのですが最近C++触っていないので、ご教授もらえると幸いです ↩
-
現状のstreamがC#とLinqと同様に使いやすいかと言われるとそれはそれで、、、となりますが、 ↩
-
ちなみにJavaに限れば、Jetbrains製IDEを使うと、For文を勝手にstreamに書き換えたりできるので、IDEの力に頼るのも一つの手だと思います ↩
-
うまく使えると入ってない(例外が鬼難しいのはJoel先生の言を待つまでもなくそのとおり ↩
正直、どの項目も過去いろんな場所で議論し尽くされた話題で、みんなウンザリしてるんだと思います。
@dairappa
元記事を書いたものです。
丁寧でわかりやすい記事をありがとうございます。
"悪いコードを駆逐したい"、ではなく、"古い書き方"(もともとはおじいコード)と書いたのは、
『書き方が悪いのではなく、時流や言語に合わない』という思いがあったからです。
丁寧な命名も、現代では一般的に推奨されていますが、機械学習・AI技術が進歩して人がコードを修正する必要がなくなれば
「変数や関数の名前なんて考えている時間があれば、仕様を考える時間に使え」
となるかもしれません。そうなれば丁寧な命名もアンチパターンになりえます。
本記事では批判理由が分類分けをされているので、わかりやすく理解しやすいと感じました。
私も本当はそんなカッコ必要ないと思っています。なぜカッコを書いてしまったかと申しますと手癖のせいです。昔の職場では使っていませんでした。なんでこんな手癖がついているかといいますと...お察しください。
最後の一言でどれだけ救われているか、私の拙い文章力では表現できません。
ありがとうございます。
{}
についても同様に、私は省略否定派です。1行で書く利便性が無く。また、if文
の効能範囲が分かりづらいからです。()
同様に範囲を明確にし、後から、他人も見ることを加味した場合も視認性が高いと考えられます。たとえ、{}
内の処理が1ステートメントでも、{}
は省略するべきではないと思っております。これは、 投稿者ご本人自らが、先人たちと同じ土俵に上がり批判を行った からではないでしょうか?なので、他の方も同様対等に相手をされた、と言う状態かと思います。若気の至りなんだから と、逆に回答した人を批判するのは違うと思います。お互いに年齢や経験値が見える場でもありませんし、 それを言い訳にするのも違う と思います。
もう少し限定的な話をすればよかったと思いますが。古い としたために、記事の主語がでかくなってしまい、沢山の言語文化の人の琴線に触れる形になったと思います。
ruby
の様に楽しく組めることを思想としている言語もあれば。python
の様に記述者の自由を認めず、誰が書いても同様のコードになることを思想としている言語もあります。言語によって思想は様々です。実際に記事の内容も『古さ』によるものがほぼなかったため、 他の言語が主流とする文化を古いと一蹴された 形になったのだと思います。 (例えばgolangでの命名など)要は無差別乱射状態だったかと。プログラマー同士宗教戦争は絶えないものですから。もう少し照準と軸を絞った記事にされれば良かったのだと思いました。
(もう少し言えば、本当に 『古い』『時代遅れ』 となっているものって、実はなかなか多く、地雷キーワードかと思います。逆では『最近の若いモンは』ですかね。)
よくまとまっていてかつ、
元記事の筆者に対するフォローも付いてくる素敵な記事ですね
元記事を見て傷ついた方もいるんでしょうけど、
コメント欄で一斉に叩かれるのも相当つらいだろうなぁと、はたから見ていて思いました。
その点で、この手のフォローが筆者に届いているのは素敵なことだと思います。
古いコードコメントアウト問題については、
私の現場にも「Gitを導入しているのにコメントアウトを多用する人」がいます。
私より年上ですが、新しい技術を積極的に取り入れる優秀な方です。
なんなら、うちの会社にGitの導入を推し進めたのもその人なんですけど・・・不思議ですね。
記事の考察はとても参考になりました。
C言語だと(そもそもC言語などという古い言語を使うなと言われそうですが)、
ifブロック内1文だったところが2文になった時にifブロックから漏れるみたいなケアレスミスが起こりがちなので、1文であっても{}で囲んでおく癖をつけよう、というコーディングルールが割と受け入れられている様な感じがします。(主観)
ヨーダ記法なんかも多分C言語の言語仕様が諸悪の根源の様な気がしますね。
そこから見ると、Pythonがブロックをインデントで表現したり、代入式を最近まで導入しなかった点が、C言語でダメっぽかった言語仕様をコーディングルールとかプログラマが気をつけるとかではなく、言語仕様レベルでなんとかしようとした感じがするんですよね。
なので相変わらず古い言語を使う場合は役に立つコーディングルールもあるし、言語が変わればそもそもそんなコーディングルールが出てくる様な余地も無い、という事もあると思います。
ハンガリアンも、比較的強い型をつけられる言語であれば良いですが、C/C++の様な整数型同士の暗黙変換を許容してしまう言語仕様とか、システムハンガリアンは無いにしてもアプリケーションハンガリアンはそうそう否定は出来ないです。
(C言語でも構造体で包むという方法がありますが……。)
なので、古い人にとって新しい事を学べる記事にするには、ある言語だと仕方ない様なコーディングルールが、この言語だとそもそも不要になる、みたいな観点で書かれると良いのかなと思います。
言語の明記は必須だと思います。
からの考察が好きです(これも好みの問題かもしれない可能性含めそう表現しておきます)
これだけ議論ができるという点で元記事もこの記事も有意義だと思います。
管理者と開発者の目線の違いですね。管理者は開発者としての経験がない人も多い、どうしても政治的な側面も出てしまうので、仕方ない部分もあるんですよね。
スラド見てたらちょうど8年前に{}を使わなかったことによるセキュリティバグの記事が出てきて興味深かった。
https://apple.srad.jp/story/14/02/24/094232/
@ryuichi1208 Typo指摘いただいたので修正します。ありがとうございます!
タイポ修正 by ryuichi1208 2021/02/25 01:00
過去のソースのコメントアウトは勘弁してほしい。
余分に行が増える
差分が見辛い
分岐の中で残されると、致命的に可読性が悪化してバグの温床に・・
悪しき記述(と感じるもの)にはいくつか原因があり
などなど。様々な事情・経緯があります。
これに関しては、一行で記述すると条件を満たす場合のみデバッガーで停止できない。ということが以前ありました。
また条件を満たす場合の処理を追加する可能性を考慮すると一行で書くメリットは見た目以外ないと思っています。
開発従事者は皆リーダブルコードを一読してもらえると、ちょっと幸せになれると思いますね
面白い記事ですね。私もおじさんなので、怒られるのかとドキドキしながら拝見しました(^^;) ←顔文字がすでに古い!
過去のソースのコメントアウトは何かあった時になぜそうなったかを説明する為なんですよ(上の組織が理由の報告を求めたりするので、いつだれが何の意図でそうしたのか示さないといけなかったり...。無駄な作業ですよねー)。ソース管理だと変更があったところを探すのが少しめんどいかな。でも、ソースが見にくいので、過去のものはこっそり消したりしています。
そうそう、無駄な省略ですが、古いコードではドキュメントの見た目のきれいさの為に、ファイル名や関数名が意味の省略名(文字数固定)と通し番号になっているものがありますよ。確かにドキュメントは記述が揃っていて見た目がきれいにはなりますが...。これは殺人的に読みにくい。ファイルや関数の意味は別途ドキュメントやコメントを見ないと全く分からない!絶対にやめてほしい!!!!
あと、省略はOracleなんかのテーブル名や列名の文字数制限とかの影響もありました。
ハンガリアン!なつかしいですね。ヨーダ!知りませんでした。
思い出したのですが、オブジェクト指向が出始めた時は「if文を書くのはへたくそ!」なんてのもありました。「分岐は可読性を落とすのでクラスの継承とメソッドのオーバーロード、オーバーライドを利用してif文をなくすべきだ!」なんて。その結果クラス階層が深くなって余計に可読性を落とすようなことも発生してましたが...。
いろいろ書きましたが、こうやって書き方とかも考えていくのは良いことですよねー。私も常に見やすいソースを考えていますが、すぐにぐちゃぐちゃになってしまいます。開発は常にあらゆることに試行錯誤です。こういうことを議論するのは楽しい。