人は1行の記述で死ぬこともある
公開からコンスタントに閲覧をいただいている以下の記事。需要がありそうなので、もう1つ記事を書いてみようと思う。
題材は前回同様のJavaScriptのクソコードではあるが、前回は冗長を極めたクソコードであるのに対し、今回はたった1行である。
1行なのに、なんと示唆深い(決してほめていない)コードだろうか。
不可思議なコード
では、実際に見てみよう。
return a = a >= 10 ? 10 : a >= 5 ? 5 : a >= 3 ? 3 : a >= 2 ? 2 : 1, z * a;
これを見て、何が return
で返されるかわかります?
代入演算子
最初見たとき、代入文 a = ×××
の評価値って何になるのかな? a
が return
されるのかなと思いました。
割り当て操作は、割り当てられた値として評価されます。
JavaScriptの仕様を確認し、「return a = ×××
は、a
がローカル変数である限り、 return ×××
ということか、冗長だな」と判断しました。
しかし、このコードは a
を返すことはありません。
閑話休題(?) 三項演算子
この1行の大半を占める三項演算子 ? :
は、もはや筆者には見慣れたものだ。規約で使うなといわれることが多いが、私はそれほど抵抗はない。
しかし、演算子の優先順位が重要なこの例示の1行では、 ?
の前の条件式部分には、せめて ()
で括って意味のまとまりを見せておきたいかな?
(a >= 10) ? 10 : (a >= 5) ? 5 : ………
三項演算子のネストがひどいのではないかという読者も多いだろうが、筆者はこれを見たときにはそんなに感情は動かなくなった。a
を対象として比較する一貫性があるので、まだ見られるコード。
カンマ演算子(コンマ演算子)
この行の最後は、1, z * a
で終わる。私は当初、すべての三項演算子で 偽 と判定されたら、z * a
が返されるのかなと思っていた。カンマ演算子の仕様を見てみよう。
それぞれの演算対象を(左から右に)評価し、最後のオペランドの値を返します。
ただ、筆者の理解は間違っていた。このカンマ演算子は、優先順位が最低位の演算子であることを見落としていた。しかも、前方の 代入演算子 =
よりも後に評価される。
つまり、1, z * a
をカタマリとして理解してはダメで、最後のオペランドとしては z * a
のみであり、return
される対象は、return
から一番遠い、z * a
なのだ。
,
の前の a = a >= 10 ? 10 : a >= 5 ? 5 : a >= 3 ? 3 : a >= 2 ? 2 : 1
は、返す値を決めるために計算しているだけだったのだ。
いみじくもWikipediaを見ていると、以下のようにある。
左被演算子を評価しその値を捨て、その後右被演算子を評価する演算子である。
だいぶ後ろに記述されたカンマ演算子ごときでその値が捨てられるのである。頭から机上デバッグしていたレビューアーは、「捨てんのかい!」とツッコミを入れずにはいられない。
1行にすることの罪
1行で何でもしようとすると、デバッガでブレークポイントを置きづらいのも苛立つポイントである。デバッグ目的で2行以上に変更すればブレークポイントも貼れるのだが、改行によって処理が変わらないか確認するのも難しい。
処理の順序
前述の不可思議なコードを書き直してみよう。
if(a >= 10) {
a = 10;
}
else if(a >= 5) {
a = 5;
}
else if(a >= 3) {
a = 3;
}
else if(a >= 2) {
a = 2;
}
else {
a = 1;
}
return z * a;
上記は、解釈しやすいように記述しているので、行数でいえば冗長かもしれない。ただし、return
で何が返ってくるのはz * a
とわかりやすくなった。
せめて下に示すぐらいには分けられなかったのかね。
const b = ((a >= 10) ? 10 : (a >= 5) ? 5 : (a >= 3) ? 3 : (a >= 2) ? 2 : 1);
return z * b;
そうそう、別に a
に代入する(a
に書き戻す)必要はないよね。
まとめと学び
- 代入文の評価値は、代入した値である
- 三項演算子はネストもできる
- カンマ演算子の評価値は、最も右にあるオペランドである
- 可読性の観点から1行書く限度がある
この1行は、結果的に学びにはなった。だからといって感謝はしていない。
コメント
@fujitanozomu(藤田 望)
8 
@www-tacos
6 
@jaque(編集済み)
2 
@so_asa5 
@otn1 
@Nabetani(鍋谷 武典)(編集済み)
0 
@albireo
Cで,が使われる典型例
0 
@i_tatte(いたて)
4 
@belq0 
@makoto-developer(makoto-developer)0 
@D-Three
0 
@ashworth(ash worth)
0 
@ashworth(ash worth)0 
@ashworth(ash worth)0 
@ashworth(ash worth)1 
@ashworth(ash worth)(編集済み) 
0 
@otn(編集済み) 0 
@KMKZ
0 
@jaque1 
@albireo- (文法的には)一つの式扱いになる
- 式の値としては右側の式の値となる
0 
@KMKZ(編集済み)
0 
@ashworth(ash worth)(編集済み) 

0 
@ashworth(ash worth)
0 
@ashworth(ash worth)
0 
@ashworth(ash worth)0 
@KMKZ(編集済み)
0 
@albireo0 
@ashworth(ash worth)(編集済み) 0 
@ashworth(ash worth)
0 
@ashworth(ash worth)
0 
@ashworth(ash worth)0 
@ashworth(ash worth)
0 
@Nabetani(鍋谷 武典)(編集済み) 0 
@ashworth(ash worth)
0 
@ashworth(ash worth)(編集済み) 0
さっさとreturnしちゃうのが良いんじゃないですかね。
おもしろさの中に学び(カンマ演算子)もあって良い記事でした
個人的には、ネストする三項演算子は改行とインデントが必須だと思っています。
https://qiita.com/yuba/items/9832c26dc1498e0017bb
なので今回のコードもいい感じに整形したら意外とありかも?と思って書いてみましたが、やっぱりダメですね。(再代入とカンマ演算子がksすぎる)
@fujitanozomu さん
おっしゃる通り、私もすぐに返すでいいかなとも思いました。
私なら最後の
return
は、1倍であることを明示するために、以下のように書きたいですかね。書き忘れだと思っちゃいそうだし・・@www-tacos さん
果敢なチャレンジ、ありがとうございます
そもそも
return
の後ろにカンマ演算子を置くのが、アンチパターンですね。個人的には、インデントはVSCodeからフォーマッタに任せたいので三項演算子は1行で書き、記事にある通り、三項演算子のまとまりをカッコで括りたい派です。
それで見づらいようなら
if
文を使ってしまうかも。minifyされたコードではこのカンマの使い方は基本的なテクニックですね。知っているとプロダクションビルドされたコードが読みやすくなります。
returnなのにaに代入してる時点で「後ろのほうで参照するんだな。これは心して読まねば」と察するべきということでしょうね
私としては、左が大きい不等号つらい、不要な再代入は避けたい、一行が長い、括弧があったほうがいい、という印象は持ちましたが、それほどわかりにくいとは感じませんでした。(個人の感想です)
初見で苦なく理解できました。倍率の計算とかですかね。
条件演算子の部分は左から順に読めばわかるので高難易度とは思いませんでした。
難易度が高いと感じるのは下記のようなものです。
それと
a
がローカル変数であればで済むはずなのに、わざわざ
としているので、
return
後もa
の値が活きるのかもしれないと想像していまして、もしそうなら、そのための代入がreturn
内で行われている点がひどいと感じます。蛇足ですが、PHP は三項演算子の優先順位が違う(ことがある?)ので要注意です。
参考になれば幸いです。
このコンマ演算子はC由来のもので、式しか書けない場所に複数の処理を書いたりするのに使われてました
でもCの文法をベースにしているjavaやC#が採用しなかったように、問題点も認識されていました。
javascriptがそんな
,
演算子を採用したのは意図的なものと言ってよさそうなので、考え方の違いを感じられる箇所ですね。@otn さん
「returnの右辺に代入文」の実用的な使われ方として思い浮かぶのは「返値を変数にも保存しておきたい」を一行で済ませるといったケースですね。
たぶんこのコードを書いた人は関数型プログラミング言語を書いてきた人なんでしょうね。
みたいな処理をそのまま直訳して
と書いてしまったような感じがします。関数型として見ればかなり自然な書き方なので。
仮にこれをレビューする立場になったら、
可読性/メンテナンス性が低いので冗長な記述に変えてください
くらいしか浮かばなかったけど(主観だから心のなかで反論されそう)
デバッグしにくいってのもあるのか
これをASTに変換するとどういう式になるんだ。冗長なコードとあまり変わらない?
これは...何をしたいかなんとなくわかるけど、もっとこう...あるだろうってなりますね。
頭がまともならこうじゃねぇの?
あまりにもバカらしいので動くかどうか検証してないけど。
あれかな? 掛け算には順序があります!とか言ってる脳みそウニな小学校算数信奉者なんかな?
なんでreturnするだけでカンマがどうしたいう話になるんやろ?
https://qiita.com/jaque/items/b99ed9dce78cc64fa9d2#comment-df9cc431b87878f1d3dc
これ、酷いなぁ…。
こんなのにいいねが6もつくんや…。
世も末やなぁ…。
分かったようなこといってるけど、
変数定義したら負けなんか?君らの知能だと…。
頭痛くなってくるわ…。
必要もないカンマ演算子とやらを使っただけで
「おー!興味深いですねぇー!!!」
とか盛り上がるから
世間一般からは『プログラマはキモい』って思われてるし、
全く信頼されてないんよなぁ…。
「エンジニア不足です」って言われてる“エンジニア”は、
そいうキモオタの事じゃないねんけどなぁ…。
はぁ!?
何を言ってんねん…。頭痛くなってくるわ…。
上の文章と下の文章で整合性取れてないやろ。
分けるべきが結論なら何に代入しようが関係ないやん。
ほんま、力抜けるわ…。
自分> @otn 2024-03-08 07:58
記事読んだのは寝る前だったので読み違えてたようです。
最後、
a*a
かと思ってコメント書いたけど、z*a
だったら変数に代入する意味ないですね。ちょっと意図を問い詰めたいレベル。カンマの後が
a*a
ならありかと思います。自分では書かないと思いますが。(今度は間違いなく「投稿する」を押す!)
JavaScriptについては素人で恐縮なのですが、なんとなくアセンブリにおけるアキュムレータレジスタを感じさせる興味深いサンプルプログラムだと思いました。
本題とはそれますが、確かにメモリ節約が不要な環境であれば変数
a
に戻す意味はありません。また今回のようにたかだか新たに宣言する64bitの変数
b
がメモリを使い果たすだなんて考慮をする必要はないとも思います。ですが、プログラムとは文脈。
おそらく変数
a
とはreturn
文より前になんらかの計算をした結果であり、「計算結果を特定の変数に格納しておく」というルールを設けているのであればあり得る代入なのではないかと考えました。変数をわけるのも可読性。
ですが、計算結果が常に変数
a
にあるということがわかるのもまた可読性です。……それにしても条件演算子入り乱れ、とどめのカンマ演算子は遠慮したいところですけどね(苦笑)
ありがとうございました。
@ashworth
わたしはswitch-caseに似た構造になっている式に感心したのですが、設計やテストを業務でなさっているという立場として、レビューをするならばやはり複雑な疑似switchな書き方よりも多くのネストが要求される書き方の方がありがたいものでしょうか?
当記事筆者です。
想像以上の広まり方に何をしてよいやらドギマギしています。
この1行のコードの破壊力をいまさら思い知らされました。
エゴサで収集した反応も含めて見てみると、
多くの発狂に同意するコメントの中に、
すんなり読めたやこんなので発狂しているようではとのコメントあり、
経験の違いを感じています。
あとは三項演算子ネストを改行とインデントがあればOK派が多いのにも驚きました。
バックボーンの違いでしょうね。
テキストエディタで開発すると改行が意味を持つのかと推察しています。
IDE前提の環境にいる私は、対応するカッコの強調表示に頼ることが多いです。
カッコで未来の自分に意図を伝える目的もあります。
諸先輩に聞きたいのですが、
return の後ろにカンマ演算子で区切ったステートメントを記述し、最右のステートメントを返すことに有用性があるかどうか知りたいのです。
事例はJavaScriptがほしいですがC言語などでも構いません。
JavaScriptのminify後のコードは、数回解析した経験がありますが、見たことがなくて・・
例えば、
a=b+5;return a;
と、return a=b+5,a;
はどちらも15文字で、スクリプトの大きさは一緒です。C++だとインライン関数は速度が速いですが、後者の1文の書き方の方が速いとかあるんですかね?
お付き合いいただけるようでしたら、ご回答お待ちしてします。
@jaque さん
答えは「コンパイルすれば同じコードになる」です。
,
演算子の左側の式を実行することによって右側の式に影響が出ることがありうるため、左側の実行が完了した後でないと右側は実行されません。(依存関係がないことが明確なら最適化のため順番を入れ替える可能性はありますが、そのような最適化は
;
で区切った場合でも行われることです)そのため
;
区切りとの違いはしかなく、コンパイルした結果はほぼ確実に同じものになります。
javascriptのようなスクリプト系言語でも、今どきのものなら構文解析より後の処理では違いがなくなっているでしょう。
概ね @albireo さんと同意見なのですが、1つの式となることにもう一つメリットがあるとするならばブロック文の
{}
を省略できることです。カンマ演算子は1つの式と解釈されるため、
{}
で囲む必要はありません。したがって
を
と記述可能であるため実際には2字の節約となります。
一方でセミコロンを付与すると複数行と解釈されるため、
{}
で囲むことが必須となります。この2字に優位性があるかと問われると……わたしなら可読性が低いとみなして不許可ですね……
@KMKZ
んーとさ、まずさ、記法や記号の一貫性が無いじゃん、それ。
三項演算子の意味からも逸脱してるし。
これが論理的な側面からの否定な。
次、機能的な側面からの否定やけど、
記法っていうのはある特定のパターンの時しか有効に機能しないのであれば意味も価値もないから使えへんねんな。
簡単な例をあげるとさ、
そのやり方でさ、これを書くなら
こうなるって言ってる?
これが果たして直感的で読みやすいと思っちゃう?
三項演算子から逸脱した意味付けなので、インデントを考えるのかなり大変やったんやけど、
こんな記法、現実的に書いてられると思うけ?
普通に、こう書いた方が良くないけ?
まかり間違って、コンマゼロゼロゼロゼロ何秒を短縮できたとして、
そこに費やす人材的、管理的、時間的コストがペイ出来るニーズが
一体この世の中のどこに何万分の1くらいの確率で存在しているか…、
やなぁ…。
まぁ、短縮できないってほかの人が答えてるからもはや自己満足以外の何物でもないただのゴミコードが答えなんやけどなぁ…。
この言語知らないんやけど、
たったこれだけの事になんでこんな呪文の詠唱みたいな事せなあかんねんw
@D-Three さんがアカン言うてるんではなくな、
こんなコード書いたらそれはそれでスリッパで頭ひっぱたくわ。
ごめん、考えれば考えるほど突っ込みが湧いてくるわ…。
これ、JSの話やったやん…。
JSでそんな微細な速度差の話、するか???
@ashworth

さまざまな幅の空白文字とタブ文字が織り成すとてもわかりやすいコードですね。
冗談はさておき、「三項演算子の意味からも逸脱」とは何をもってして逸脱しているのか言語化されていませんのでわかりませんが、三項演算子――いいかえて条件演算子には「
if
、then
、else
をコンパクトに書く」という意味以上はないと考えます。したがって、そもそも複雑な式を条件演算子に任せている時点で逸脱しているのではないかというのが私の見解です。
それは、今回問題としているコードに関してもそうです。
記号に一貫性がないことに関しては、例示に関しては記号よりも「条件を左、結果を右」にするという一貫性は取れています。
逆に言えば記号でそろえると条件と結果の記述場所に一貫性は取れません。
ところで、SQLを少しだけ触れている身としましては下記のような書き方をみることがあります。
本来この程度であれば
gender
はcase
の後に書いてwhen
には値しか書きませんが、例えばです。また予約語か演算子かという違いはありますが、不統一という点においては
then
とelse
が入り混じっているので同じでしょう。「インデントを考えるのかなり大変」とありますが、たとえばOracle Databaseの公式クライアントであるSQL DeveloperではSQLの自動整形を実行すると上記に近い形に整形される場合があります。
こういった書き方を意識したものであるのだろうと考えます。
さてお示しになているコードについてですが、実際には括弧を外すことでより可読性が低くみえるバイアスがかかってしまっていますので、踏襲することを意識しながら修正しますと下記の通りになるかと思われます。
はい、やはり見にくいですね。せいぜい1層にできる条件演算子が限界でしょう。
しかし現実的かという論に対して申しますと「
if
、then
、else
」の階層を意識づけたプログラムも変わりはありません。「普通にこう書いた方が」とありますが、複雑になるほど深いインデントを毎度つける。
そして条件が変わればインデント階層を浅くしたり深くしたりしながら修正をする。
どっちもこの過程は変わりません。
はっきりいって、どっちも書かないほうがいいです。
悪例のための悪例と言わざるを得ません。
とはいえ、一般的なコードエディタには前行のインデントを引き継ぐ機能もありますのでインデントをどこまでも真剣に考える必要がある状況はどれほどあるのか、という話にはなります。
※先ほどのコメントでは「多くのネスト」と書いてしまいましたが正しくは「多くのインデント」でした、失礼しました
@ashworth さん
それより、そんなに書くことがたくさんあるならコメント欄に連投ではなく「この記事にリンクを貼った自分の記事」として書くことをお勧めします。
あなたの見解にはあまり興味がない人にとっては、あなたのコメントが多すぎてコメント欄を読むのが苦痛になるレベルなので。
@KMKZ
あー、頭があれなのか…。
じゃぁ、説明しても無駄か…。
多分、ろくにコード書いたことないんだろうけど、
これから君のやり方で百万のコードを書いていけば自分の思慮の浅さをりかいするんじゃないかな。
書かないから理解しないまま終わると思うけど。
俺のやり方は真似して書いてる奴がネットでもたまに見受けられるけど、
君のやりかた真似する奴は一切現れないやろなぁ。
センスおかしいから。
三項演算子とは何なのか、から説明しないとダメなのか…。
凄いな…。
え? え?
ごめんなさい、今、一体どこの速度差の話してるの??????????
ごめんなさい、え? え????
フロント界隈って、こんなんなんだよなぁ…。
気が遠くなるわ…。
本人も「冗談だ」って言っているからいいけどさ、
ごめんな、もう、全体的にアホらしいのでお酒飲みながらメモ帳で書いてるから知らん。
そこに意味がある話題でもないし。
関連する記事を書きました。
https://qiita.com/Nabetani/items/fa84c8eb817fe6f18a5a
それと @jaque 様が
と記載されてます。
ミスリーディングではないので困らないところですが、用語としては 「
return a=b+5,a;
」は、文(ステートメント)で、その中にあるa=b+5,a
・a=b+5
・a
は、式 だと思います。参考になれば幸いです。
あー、
平衡感覚がマヒしてるんやろなぁ…。
必要もないものを必要もない形に歪ませることで
その理解と取扱いを難解にせしめることは、
事物の私物化と独占化であって、
通常、プログラムを書くという事は商業活動の一環やので、
それを私物化するということは、
顧客が獲得すべき利益の私物化を企む愚劣な奴のする事やねんな。
基本、そういう愚劣な輩は経済の場からは排除されるものやが、
たまに管理者の能力が劣っていると排除されず残り、
その商業モデルが死に絶えるまでその害悪が蛭のごとく居座って養分を吸い上げ続ける地獄が出来上がるんよな。
その場合、大抵、もっとも養分を吸い上げられつづけるのは
何も知らない「お客様」やな。
その商業モデルが死に絶えたら蛭は次の獲物を探す。
大抵の一般人は蛭の恐ろしさを認知できないから平然とその蛭を迎え入れてしまう。
地獄絵図や。
くわばらくわばら。
@albireo
君に言ってるやけど、
理解できるだけの良心、まだ残ってるか?
いいね以上の気持ちはコメントで