ワンライナー対読みやすさ:コードの削減を停止するタイミング [閉まっている]


14

環境

私は最近、より良いフォーマットのコードの作成に興味を持ちました。そして、「コードを実行するための独自の「最良の」方法はもちろんないため、「良い慣行と見なすのに十分な人々によって承認されたルールに従う」という意味です。

最近はほとんどRubyでコードを書いているので、リンター(Rubocop)を使ってコードの「品質」に関する情報を提供し始めました(この「品質」はコミュニティ主導のプロジェクトruby-style-guideで定義されています)。

場合によってはコードの効率がコードの記述方法によって実際影響を受ける場合でも、コードの効率についてではなく、「フォーマットの品質」のように「品質」を使用することに注意してください。

とにかく、すべてのことをして、私はいくつかのことを実現しました(または、少なくとも覚えていました)。

  • 一部の言語(特にPython、Rubyなど)では、優れたコードのワンライナーを作成できます。
  • コードのガイドラインに従うことで、コードを大幅に短くすることができますが、それでも非常に明確です。
  • ただし、これらのガイドラインに厳密に従うと、コードがわかりにくく/読みやすくなります。
  • コードはいくつかのガイドラインをほぼ完全に尊重し、それでも品質が低い場合があります
  • コードの読みやすさはほとんど主観的です(「私が明らかにすることは、仲間の開発者にとって完全に不明瞭かもしれない」)

これらは単なる観察であり、もちろん絶対的なルールではありません。また、コードの読みやすさと以下のガイドラインはこの時点では無関係に見えるかもしれませんが、ここでのガイドラインはコードの1つのチャンクを書き換える方法の数を絞り込む方法です。

次に、いくつかの例を挙げて、すべてをより明確にします。

単純なユースケースを見てみましょうUser。「」モデルを持つアプリケーションがあります。ユーザーはオプションでありfirstnameかつsurname必須とemailアドレスを。

私は、メソッド「書きたいnameその後、名前を返します」(firstname + surname少なくとも、彼の場合は、ユーザーのを)firstnameまたはsurname、本、またはそのemail代替値としていない場合。

また、このメソッドでuse_emailパラメーターとして" "(ブール値)を使用して、フォールバック値としてユーザーの電子メールを使用できるようにします。このuse_emailパラメーターは(渡されない場合)デフォルトで" "になりますtrue

それをRubyで記述する最も簡単な方法は次のとおりです。

def name(use_email = true)
 # If firstname and surname are both blank (empty string or undefined)
 # and we can use the email...
 if (firstname.blank? && surname.blank?) && use_email
  # ... then, return the email
  return email
 else
  # ... else, concatenate the firstname and surname...
  name = "#{firstname} #{surname}"
  # ... and return the result striped from leading and trailing spaces
  return name.strip
 end
end

このコードは、最もシンプルで理解しやすい方法です。Rubyを「話さない」人でも。

それでは短くしてみましょう:

def name(use_email = true)
 # 'if' condition is used as a guard clause instead of a conditional block
 return email if (firstname.blank? && surname.blank?) && use_email
 # Use of 'return' makes 'else' useless anyway
 name = "#{firstname} #{surname}"
 return name.strip
end

これは短く、理解しやすいですが、簡単ではありません(ガード句は条件ブロックよりも読みやすいです)。また、Guard句により、使用しているガイドラインへの準拠が強化されるため、ここではwin-winです。また、インデントレベルを減らします。

次に、Rubyのマジックを使用して、さらに短くします。

def name(use_email = true)
 return email if (firstname.blank? && surname.blank?) && use_email
 # Ruby can return the last called value, making 'return' useless
 # and we can apply strip directly to our string, no need to store it
 "#{firstname} #{surname}".strip
end

さらに短く、完全にガイドラインに従っています...

質問を開始できるのはここです。本当に価値があるのでしょうか?「いいえ、読みやすくし、 ' return' を追加します」と言う必要があります(これはガイドラインを尊重しないことを知っています)。それとも、「大丈夫、Rubyのやり方、気のいい言語を学んでください!」

オプションBを使用する場合は、さらに短くしてください。

def name(use_email = true)
 (email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end

ワンライナーです!もちろん、それは短いです...ここでは、Rubyが値を返しますという事実を利用する(電子メールは以前と同じ条件で定義されることになるので)1が定義されているの応じて、他の。

書くこともできます:

def name(use_email = true)
 (email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end

それは短く、読むのはそれほど難しくありません(つまり、私たちはallいワンライナーがどのように見えるかを見てきました)、良いRuby、それは私が使用するガイドラインに準拠しています...しかし、まだ最初の書き方と比較してそれは、読むのも理解するのもずっと簡単ではありません。また、この行が長すぎる(80文字以上)と主張することもできます。

質問

いくつかのコード例は、「フルサイズ」コードとその縮小版の多く(有名なワンライナーまで)を選択するのが難しいことを示しています。それでも、読みやすさの点で「フルサイズ」コードに勝るものはありません...

そこでここに本当の質問があります:どこでやめるか?短いとき、十分短い?コードが「短すぎ」、読みにくくなったことを知る方法(非常に主観的であることに留意してください)?そしてさらに:常にそれに応じてコーディングし、ワンライナーと「フルサイズ」のコードチャンクを混在させないようにする方法

TL; DR

ここでの主な質問は、「長くても明確で、読みやすく、理解しやすいコードチャンク」と「強力で短く、読みにくい1ライナーを理解する」のどちらかを選択する場合です。 2つの唯一のオプションではなく、スケールの最下部:「十分にクリア」と「本来あるべきほど明確ではない」の境界をどこで定義するか。

主な問題は、古典的な「ワンライナー対読みやすさ:どちらが良いですか?」ではありません。しかし、「これら2つのバランスを見つける方法は?」

編集1

コード例のコメントは「無視」されることを意図しており、何が起きているかを明確にするためにここにありますが、コードの可読性を評価する際には考慮されません。


7
答えが短すぎる:前の反復よりも良いかどうか分からなくなるまで繰り返しリファクタリングを続けてから、最後のリファクタリングを停止して元に戻します。
ドム

8
キーワードを追加したreturnバリアント3 好きです。これらの7文字は、私の目にかなり明瞭さを加えます。
cmaster-モニカの復元18年

2
あなたが本当に恐ろしいと感じているなら、あなたは全部を[firstname,surname,!use_email].all?(&:blank?) ? email : "#{firstname} #{surname}".strip...と書くことができfalse.blank?ます。なぜなら、trueを返し、三項演算子が数文字を節約するからです...¯\ _(ツ)_ /
¯– DaveMongoose

1
OK、私は尋ねる必要があります:returnキーワードはどのような明確さを追加することになっていますか?!情報一切提供しません。それは純粋な混乱です。
コンラッドルドルフ

2
簡潔さは明快さを生むという概念は、収益の減少の法則に苦しむだけでなく、極端にプッシュされると逆転します。短い関数を短くするために書き直している場合は、時間を浪費していることになります。同じことが練習を正当化しようとする場合にも当てはまります。
sdenham

回答:


26

どんなコードを書いても、可読性が最高です。ショートが次善です。通常、読み取り可能とは、コード、適切な名前の識別子、およびコードが記述されている言語の一般的なイディオムを理解できるように十分に短いことを意味します。

これが言語にとらわれない場合、これは間違いなく意見に基づいていると思いますが、Ruby言語の範囲内で答えることができると思います。

まず、Rubyを作成するための機能と慣用的な方法はreturn、メソッドから早期に返されない限り、値を返すときにキーワードを省略することです。

別の機能とイディオムを組み合わせると、末尾のifステートメントを使用してコードの読みやすさが向上します。Rubyを推進するアイデアの1つは、自然言語として読み取るコードを書くことです。このために、_whyのRubyのPoignant Guide、第3章に進みます。

以下を声に出して読んでください。

5.times { print "Odelay!" }

英文では、句読点(ピリオド、感嘆符、括弧など)は黙っています。句読点は、単語に意味を追加し、著者が文によって意図したものに関する手がかりを与えるのに役立ちます。上記を5回印刷して「Odelay!」と読みましょう。

これを考えると、コード例#3はRubyで最も慣用的です:

def name(use_email = true)
  return email if firstname.blank? && surname.blank? && use_email

  "#{firstname} #{surname}".strip
end

コードを読むと、次のようになります。

名が空白で姓が空白の場合は電子メールを返し、電子メールを使用します

(返す)名と姓を削除

これは実際のRubyコードにかなり近いです。

実際のコードは2行しかないため、かなり簡潔で、言語のイディオムに準拠しています。


ナイスポイント。質問がRuby中心であることを意図していなかったのは事実ですが、ここでは言語にとらわれない答えを持つことは不可能であることに同意します。
-Sudiukil

8
私は、コードを自然言語のように非常に過大評価する(そして時には問題になることもある)という考えを見つけました。しかし、この動機がなくても、この答えと同じ結論に達します。
コンラッドルドルフ

1
コードに対して行うことを検討するもう1つの調整があります。つまりuse_email、関数呼び出しではなく変数なので、他の条件の前に配置します。しかし、それでもストリング補間は違いを一​​掃します。
ジョンドヴォルザーク

自然言語構造に従ってコードを構造化すると、言語のtrapに陥る可能性があります。たとえば、次の要件を読むとき、おそらくコーディングが簡単な場合do send an email if A, B, C but no D、前提に従って2つのif / elseブロックを入力するのが自然でしょうif not D, send an email。自然言語を読むときは注意して、コードに変換してください。「Neverending story」の新しいバージョンを書くことができるからです。クラス、メソッド、および変数。結局のところ、大したことではありません。
ライヴ

@Laiv:自然言語のようにコードを読むことは、要件を文字通り翻訳することを意味しません。それは、コードを書くことを意味するので、声を出して読むとき、読者はコードのすべてのビット、文字の文字、言語構造の言語構造を読むことなくロジックを理解できます。コーディングのif !D方が優れている場合Dは、意味のある名前が付いているので、londで十分です。そして、!オペレーターが他のコードの中で迷子になった場合、識別子を呼び出すNotDことが適切です。
グレッグブルクハート

15

「最善の判断を下す」よりも良い答えが得られるとは思わない。要するに、よりも明快さのために努力すべきです。多くの場合、最短のコードも最も明確ですが、短さを達成することにだけ集中すると、明確さが損なわれる可能性があります。これは明らかに最後の2つの例に当てはまり、前の3つの例よりも理解するためにより多くの努力が必要です。

重要な考慮事項は、コードの対象者です。もちろん、読みやすさは読む人に完全に依存しています。あなたがコードを読むことを期待している人々(あなた自身の傍ら)は実際にRuby言語のイディオムを知っていますか?まあ、この質問はインターネット上の人がランダムに答えられるものではなく、これはあなた自身の決定に過ぎません。


オーディエンスのポイントには同意しますが、それは私の闘争の一部です。私のソフトウェアはオープンソースであることが多いため、オーディエンスは初心者と「ルビーの神」によって構成される可能性があります。ほとんどの人が簡単にアクセスできるようにすることはできますが、言語が提供する利点を無駄にしているように感じます。
スディウキル

1
本当に恐ろしいコードを引き継ぎ、拡張し、維持しなければならなかった誰かとして、明快さが勝たなければなりません。古い格言を覚えておいてください-メンテナーがあなたがどこに住んでいて、子供たちが学校に行くのかを知っている復の地獄の天使であるかのようにコードを書いてください。
uɐɪ

2
@Sudiukil:それは重要なポイントです。初心者がとにかくオープンソースコードに貢献する可能性は低いので、その場合は慣用的なコードに努力することをお勧めします(つまり、言語の十分な知識があると仮定します)。(または、彼らがそうするならば、彼らは言語を学ぶ努力をする準備ができているでしょう。)
JacquesB

7

ここでの問題の一部は、「読みやすさ」です。私にとって、最初のコード例を見てみましょう。

def name(use_email = true)
 # If firstname and surname are both blank (empty string or undefined)
 # and we can use the email...
 if (firstname.blank? && surname.blank?) && use_email
  # ... then, return the email
  return email
 else
  # ... else, concatenate the firstname and surname...
  name = "#{firstname} #{surname}"
  # ... and return the result striped from leading and trailing spaces
  return name.strip
 end
end

また、コードを繰り返すだけの「うるさい」コメントがたくさんあるため、読みにくいと感じています。それらを取り除きます:

def name(use_email = true)
 if (firstname.blank? && surname.blank?) && use_email
  return email
 else
  name = "#{firstname} #{surname}"
  return name.strip
 end
end

そして今ではずっと読みやすくなっています。それを読んで、「うーん、Rubyが三項演算子をサポートするのかしら?C#では、次のように書くことができます。

string Name(bool useEmail = true) => 
    firstName.Blank() && surname.Blank() && useEmail 
    ? email 
    : $"{firstname} {surname}".Strip();

ルビーではそのようなことが可能ですか?あなたの投稿を掘り下げると、次のことがわかります:

def name(use_email = true)
 (email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end

すべての良いもの。しかし、それは私には読めません。行全体を表示するにはスクロールする必要があるからです。それを修正しましょう:

def name(use_email = true)
 (email if (firstname.blank? && surname.blank?) && use_email) 
 || "#{firstname} #{surname}".strip
end

今、私は幸せです。構文がどのように機能するかは完全にはわかりませんが、コードが何をするのかは理解できます。

しかし、それは私だけです。他の人々は、読みやすいコードを作成するものについて、まったく異なる考えを持っています。そのため、コードを記述するときは、対象読者を知る必要があります。あなたが絶対的な初心者を教えているなら、あなたはそれをシンプルに保ち、おそらくあなたの最初の例のようにそれを書きたいと思うでしょう。あなたがルビーの長年の経験を持つプロの開発者の中で働いているなら、その言語を利用して短いコードを書いてください。中間にある場合は、中間に向けてください。

ただし、最後の例のように「巧妙なコード」に注意してください。自問[firstname, surname].all?(&:blank?)してみてください。たとえ読みにくくなったとしても、それはあなたのスキルを見せてくれるので、あなたが賢く感じる以外に何かを追加しますか?この例は、おそらくそのカテゴリに完全に対応すると思います。ただし、5つの値を比較している場合は、良いコードだと思います。繰り返しになりますが、ここには絶対的な線はありませんが、あまりにも賢いことに注意してください。

要約すると、読みやすさは、対象読者を把握し、それに応じてコードをターゲットにし、簡潔で明確なコードを書く必要があります。「賢い」コードを決して書かないでください。短くしますが、短すぎません。


2
さて、私はそれを言及するのを忘れましたが、コメントは「無視される」ことを意図していました、それらはRubyをよく知らない人々を助けるためだけにここにあります。聴衆については有効なポイントですが、私はそれについて考えませんでした。あなたを幸せにするバージョンについては、重要なのは行の長さである場合、私のコードの3番目のバージョン(returnステートメントが1つだけのもの)はそれをちょっとしており、さらに少し理解しやすいでしょうか?
スディウキル

1
@Sudiukilはルビー開発者ではないので、最も読みにくく、「別の言語の観点から」私が探していたものが「最良の」ソリューションとして適合しないことがわかりました。ただし、Rubyは最後の式の値を返す言語の1つであるという事実に精通している人にとっては、おそらく最も簡単で読みやすいバージョンを表しているでしょう。繰り返しますが、それはすべてあなたの聴衆についてです。
デビッドアルノ

Rubyの開発者ではありませんが、これは、「ここに返されるものがあります[脚注:特定の長い条件の下で」というような、トップ投票の回答よりもはるかに理にかなっています。パーティーへ。" 基本的に単なるcaseステートメントであるロジックは、単一の統一されたcaseステートメントのように記述し、一見無関係な複数のステートメントに分散させるべきではありません。
ポール

私は1つにあなたの他の支店に二つの文を組み合わせてしまう除き個人的に私はあなたの2番目のコードブロックとなるだろう:return "#{firstname} #{surname}".strip
ポール・

2

これはおそらく、意見に基づいた答えを出すのが難しい質問ですが、ここに私の2セントがあります。

コードを短くしても読みやすさに影響を与えないか、さらには改善される場合は、それを試してください。コードが読みにくくなった場合、そのままにしておくのに十分な理由があるかどうかを検討する必要があります。短い、クールだからといって、あるいはできるからといって、それを行うのは悪い理由の例です。また、コードを短くすると、作業する他の人が理解しにくくなるかどうかを考慮する必要があります。

それでは、正当な理由は何でしょうか?それは実際には判断の呼び出しですが、例はパフォーマンスの最適化のようなものです(もちろん、事前にではなく、パフォーマンステストの後)。読みやすさを低下させながら、支払いを希望するいくつかの利点が得られます。その場合、有用なコメントを提供することにより、この欠点を軽減することができます(コードが何をするのか、なぜそれを少し暗号化する必要があるのか​​を説明します)。さらに良いことに、そのコードを意味のある名前の別の関数に抽出することができます。これにより、詳細に立ち入ることなく(関数の名前を介して)何が起こっているかを説明する呼び出しサイトの1行だけですこれについての意見なので、これはあなたがしなければならない別の判断の呼び出しです)。


1

答えは少し主観的ですが、1〜2か月後に戻ってきたときにそのコードを理解できる場合は、できる限りの誠実さを自問する必要があります。

各変更は、コードを理解する平均的な人の能力を向上させるはずです。コードを理解しやすくするために、次のガイドラインを使用すると役立ちます。

  • 言語のイディオムを尊重してください。C#、Java、Ruby、Pythonにはすべて、同じことを行うための好ましい方法があります。慣用的なコンストラクトは、慣れていないコードの理解に役立ちます。
  • コードが読みにくくなったら停止します。あなたが提供した例では、コードを減らす最後のペアをヒットしたときに起こりました。前の例の慣用的な利点を失い、何が起こっているのかを真に理解するには多くの思考を必要とする多くのシンボルを導入しました。
  • 予期しない何かを正当化する必要がある場合にのみコメントを使用してください。Rubyにあまり詳しくない人に構造を説明するためにあなたの例があったことは知っていますが、それでも問題はありません。予期しないビジネスルールを説明するためにコメントを使用し、コードがそれを代弁できる場合はそれらを避けることを好みます。

そうは言っても、拡張されたコードは、何がうまくいっているのかを理解するのに役立つ場合があります。その1つの例は、C#とLINQからのものです。LINQは優れたツールであり、状況によっては読みやすさを向上させることもできますが、私はそれがはるかに混乱する多​​くの状況に遭遇しました。他の人がより良く維持できるように、式を適切なifステートメントでループに変えることを提案したピアレビューでいくつかのフィードバックがありました。私が従ったとき、彼らは正しかった。技術的には、LINQはC#の方が慣用的ですが、わかりやすさが低下し、より詳細なソリューションによって改善される場合があります。

私はこれをすべて言っています:

コードを改善できる時期を改善する(より理解しやすい)

あなたまたはあなたのような誰かが後でそのコードを維持する必要があることを忘れないでください。次にあなたがそれに出くわすときは、数ヶ月後になるかもしれません。コードを理解できるという犠牲を払って、行数の削減を追いかけないでください。


0

読みやすさはあなたが持ちたいプロパティであり、多くの1つのライナーを持つことはそうではありません。したがって、「ワンライナー対読みやすさ」ではなく、質問は次のようになります。

ワンライナーが読みやすさを向上させるのはいつですか?

ワンライナーは、次の2つの条件を満たしていると読みやすくなると思います。

  1. それらは関数に抽出するにはあまりにも具体的です。
  2. 周囲のコードを読み取る「フロー」を中断する必要はありません。

たとえばname、メソッドの名前が良くなかったとしましょう。名と姓を組み合わせること、または名前の代わりに電子メールを使用することは、自然なことではありません。だからname、最高のものの代わりに長くて面倒なことが判明しました:

puts "Name: #{user.email_if_there_is_no_name_otherwise_use_firstname_and_surname(use_email)}"

このような長い名前は、これが非常に具体的であることを示しています-より一般的なものであれば、より一般的な名前を見つけることができます。したがって、メソッドでラップしても、読みやすさ(長すぎる)やDRYness(他の場所で使用するにはあまりにも固有)のいずれにも役立たないため、コードをそのまま残しておく方がよいでしょう。

それでも-なぜそれをワンライナーにするのですか?通常は、複数行のコードよりも読みにくくなります。ここで、2番目の条件(周囲のコードのフロー)を確認する必要があります。このようなものがある場合:

puts "Group: #{user.group}"
puts "Title: #{user.title}"
if user.firstname.blank? && user.surname.blank?) && use_email
  name = email
else
  name = "#{firstname} #{surname}"
  name.strip
end
puts "Name: #{name}"
puts "Age: #{user.age}"
puts "Address: #{user.address}"

複数行のコード自体は読み取り可能ですが、周囲のコード(さまざまなフィールドを印刷)を読み取ろうとすると、複数行の構成体がフローを中断します。これは読みやすいです:

puts "Group: #{user.group}"
puts "Title: #{user.title}"
puts "Name: #{(email if (user.firstname.blank? && user.surname.blank?) && use_email) || "#{user.firstname} #{user.surname}".strip}"
puts "Age: #{user.age}"
puts "Address: #{user.address}"

フローは中断されず、必要に応じて特定の表現に集中できます。

これはあなたのケースですか?絶対にありません!

最初の条件はあまり関連性がありません-メソッドに値するほど十分に一般的であるとすでに考えており、そのメソッドの実装よりもはるかに読みやすい名前を思い付きました。明らかに、それを再び関数に抽出することはありません。

2番目の条件については、周囲のコードの流れを中断しますか?番号!周囲のコードはメソッド宣言であり、それを選択するnameことが唯一の目的です。名前を選択するロジックは、周囲のコードの流れを中断するものではありません-それは周囲のコードのまさに目的です!

結論-関数本体全体をワンライナーにしないでください

ワンライナーは、フローを中断せずに少し複雑なことをしたいときに適しています。関数宣言はすでにフローを中断しているため(その関数を呼び出すときに中断されません)、関数本体全体を1行にすることは読みやすさの向上にはなりません。

注意

私は「完全な」関数とメソッドを参照しています- 通常は周囲のコードの一部であり、そのフロー内に収まる必要があるインライン関数やラムダ式ではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.