最近、より良いフォーマットのコードを作成することに興味を持ちました。そして、より良いことは、「良い習慣だと考えるのに十分な数の人々によって承認されたルールに従うこと」を意味します(もちろん、コード化するための1つのユニークな「最良の」方法は決してないからです)。
最近、私は主にRubyでコーディングしているので、リンター(Rubocop)を使用して、コードの「品質」に関する情報を提供し始めました(この「品質」はコミュニティによって定義されています)駆動プロジェクト Ruby-style-guide )。
「フォーマットの品質」のように「品質」を使用することに注意してください。コードの効率についてはそれほどではありませんが、実際にはコードの効率はisコードの記述方法に影響されます。
とにかく、それをすべてやって、私はいくつかのことに気づきました(または少なくとも覚えていました)。
これらは単なる観察であり、絶対的なルールではありません。また、この時点ではコードの可読性と以下のガイドラインは無関係であるように見えるかもしれませんが、ここでのガイドラインは、コードの1つのチャンクを書き換える方法の数を絞り込む方法です。
ここで、いくつかの例を挙げて、これらすべてをより明確にします。
簡単な使用例を考えてみましょう。「User
」モデルを使用したアプリケーションがあります。ユーザーには、オプションのfirstname
とsurname
、および必須のemail
アドレスがあります。
少なくともname
またはfirstname
が存在する場合はユーザーの名前(firstname + surname
)を返し、存在しない場合はその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句は、使用しているガイドラインへの準拠も強化しているので、ここでメリットがあります。インデントレベルも下げます。
次に、いくつかの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ステートメントがないため、この方法に不慣れな人には少し混乱するため、あまり明確ではありません。
質問を始めることができるのはここです。本当に価値があるのでしょうか。 「いいえ、読みやすくして、 'return
'を追加してください」(これはガイドラインを尊重しないことを知っている)。それとも、「それは結構です、Ruby方法です、いまいましい言語を学んでください!」?
オプションBを採用する場合は、さらに短くしてください。
def name(use_email = true)
(email if (firstname.blank? && surname.blank?) && use_email) || "#{firstname} #{surname}".strip
end
ワンライナーです!もちろん短いです...ここではRubyが値またはを返すという事実を利用していますどちらが定義されているかに依存します(メールは以前と同じ条件で定義されるため)。
それを書くこともできます:
def name(use_email = true)
(email if [firstname, surname].all?(&:blank?) && use_email) || "#{firstname} #{surname}".strip
end
短くて、それほど読みにくいわけではありません(つまり、醜いワンライナーがどのように見えるかはすべて見てきました)、良いRuby、それは私が使用するガイドラインに準拠しています...しかし、それでも、最初の書き方と比較するとそれは、読みやすく理解しにくいです。この行が長すぎる(80文字を超える)と主張することもできます。
コードのいくつかの例は、「フルサイズ」のコードとその縮小バージョン(有名なワンライナーまで)の多くを選択するのが難しいことを示しています。これは、ご覧のとおり、ワンライナーはそれほど怖くないためですが、それでも、読みやすさの点で「フルサイズ」のコードに勝るものはありません...
だからここに本当の質問があります:どこでやめるべきか?いつ、短いですか?コードが「短すぎ」て読みにくくなったときを知る方法(かなり主観的であることを覚えておいてください)?さらに、常にそれに応じてコーディングし、「フルサイズ」のコードのチャンクと1行を混在させないようにするには、どうすればいいですか?
ここでの主な質問は、「長くて明確で、読みやすく、理解可能なコードの塊」と「強力で短く、読みやすく、ワンライナーが理解しにくい」のどちらかを選択する場合、これらの2つが一番上であり、 2つの唯一のオプションではなく、スケールの一番下:「十分に明確」と「それほど明確ではない」の間の境界をどこで定義するか?
主な質問は、古典的な「One-liners対読みやすさ:どちらが優れているか」ではありません。しかし、「これら2つの間のバランスをどのように見つけるか?」
コード例のコメントは「無視」されることを意図しており、何が起こっているのかを明確にするためにここにありますが、コードの読みやすさを評価するときに考慮されません。
どんなコードを書いても、読みやすいのが一番です。ショートは2番目に良いです。また、通常、「読み取り可能」とは、コード、わかりやすい識別子、およびコードが記述されている言語の一般的なイディオムを理解できるように十分に短いことを意味します。
これが言語にとらわれない場合、私は間違いなく意見に基づいていると思いますが、Ruby言語の範囲内で私はそれに答えることができると思います。
まず、機能と慣用的な記述方法Rubyは、メソッドから早く戻らない限り、値を戻すときにreturn
キーワードを省略することです。
もう1つの機能と慣用句を組み合わせると、末尾のif
ステートメントを使用してコードを読みやすくすることができます。 Rubyの原動力となるアイデアの1つは、自然言語として読み取るコードを記述することです。このために、_ why's Poignant Guide to Ruby 、第3章 。
以下を声に出して読んでください。
5.times { print "Odelay!" }
英語の文では、句読点(ピリオド、感嘆符、括弧など)は黙っています。句読点は単語に意味を追加し、作者が文によって意図したものについての手掛かりを与えるのに役立ちます。したがって、上記を次のように読み替えてください。「Odelay!」を5回印刷します。
これを踏まえると、コード例#3はRubyで最も慣用的です。
def name(use_email = true)
return email if firstname.blank? && surname.blank? && use_email
"#{firstname} #{surname}".strip
end
コードを読み取ると、次のようになります。
名が空白で姓が空白の場合は電子メールを返し、電子メールを使用する
(戻り)名と姓を取り除いた
実際のRubyコードにかなり近いです。
実際のコードは2行しかないので、かなり簡潔であり、言語のイディオムに準拠しています。
「最善の判断を下す」以上の答えは得られないと思います。要するに、あなたはshortnessではなくclarityに努めるべきです。多くの場合、最短のコードも最も明確ですが、短さの達成のみに焦点を合わせると、明確さが損なわれる可能性があります。これは、前の3つの例よりも理解するのに多くの労力を必要とする最後の2つの例の場合に明らかに当てはまります。
重要な考慮事項は、コードの対象者です。もちろん、読みやすさは読む人に完全に依存します。あなたがコードを読むことを期待している人々は、実際に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
_
そして、コードを繰り返すだけの「騒々しい」コメントがたくさんあるので、私は読みにくいと思います。それらを取り除く:
_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
_
今は幸せです。構文がどのように機能するかは完全にはわかりませんが、コードの機能は理解できます。
しかし、それは私だけです。他の人々は、コードを読むのが良い理由は何なのかについて非常に異なる考えを持っています。したがって、コードを書くときは、読者を知る必要があります。あなたが絶対初心者を教えるなら、あなたはそれを単純に保ち、おそらくあなたの最初の例のようにそれを書きたいでしょう。 Ruby)で長年の経験を持つ一連のプロの開発者の間で作業する場合は、その言語を利用して短い言語に保つコードを記述します。中間のどこかにある場合は、の間に。
ただし、最後の例のように、「賢いコード」に注意してください。自問してみてください。[firstname, surname].all?(&:blank?)
は、今では少し読みづらくても、スキルを披露して賢く感じる以外に何かを追加しますか?この例はおそらくそのカテゴリに完全に当てはまると思います。 5つの値を比較しているなら、私はそれを良いコードだと思います。繰り返しますが、ここには絶対的な線はありません。賢すぎることに注意してください。
要約すると、可読性には、対象読者を理解し、それに応じてコードを対象とし、簡潔であるが明確なコードを書くことが必要です。 「賢い」コードを書かないでください。短くしますが、短すぎないようにします。
これはおそらく意見に基づく回答をしないのは難しい質問ですが、ここに私の2セントがあります。
コードを短くしても可読性に影響を与えない、またはコードが改善される場合は、それを試してください。コードが読みにくくなった場合は、そのままにしておく十分な理由があるかどうかを検討する必要があります。短い、クールな、またはできるという理由だけでそれを行うのは、悪い理由の例です。また、コードを短くすると、他の人が理解しにくくなるかどうかも検討する必要があります。
それでは、何が理由でしょうか?実際には判断の呼びかけですが、例としてはパフォーマンス最適化のようなものがあります(もちろん、事前ではなく、パフォーマンステストの後)。読みやすさが低下し、代金を払っても構わないと思っているというメリットをもたらすもの。その場合は、役立つコメントを提供することで欠点を軽減できます(これにより、コードの機能と、コードを少しわかりにくくする必要がある理由がわかります)。さらに、そのコードを意味のある名前で別の関数に抽出できるため、詳細を説明することなく(関数の名前を介して)何が起こっているかを説明するのは呼び出しサイトの1行だけになります(ただし、これについての意見なので、これはあなたがしなければならない別の判断の呼びかけです)。
答えは少し主観的ですが、1〜2か月後に戻ってきたときにそのコードを理解できるかどうかは、自分が習得できるすべての正直さを自問する必要があります。
変更するたびに、平均的な人のコードを理解する能力が向上します。コードをわかりやすくするには、次のガイドラインを使用すると便利です。
とはいえ、拡張されたコードが何がより良い状況にあるかを理解するのに役立つ場合があります。その一例がC#とLINQです。 LINQは優れたツールであり、状況によっては読みやすさを向上させることができますが、非常に混乱する状況にも遭遇しました。他の人がそれをよりよく維持できるように、適切なifステートメントを使って式をループに変えることを提案するピアレビューでいくつかのフィードバックがありました。私が応じたとき、彼らは正しかった。技術的には、LINQはC#の方が慣用的ですが、理解しやすさが低下し、より詳細なソリューションがそれを改善する場合があります。
私はこれを言うためにそれをすべて言います:
あなたのコードをより良くする(より理解しやすい)ときを改善する
覚えておいてください、あなたまたはあなたのような誰かが後でそのコードを維持する必要があります。次に遭遇するのは、数ヶ月後かもしれません。コードを理解できるという犠牲を払って行数を減らすことは避けてください。
読みやすさは、必要なプロパティです、having-many-one-linersではありません。したがって、「ワンライナーvs読みやすさ」ではなく、次の質問をする必要があります。
ワンライナーは、次の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行にすることは、読みやすさに役立ちません。
私は「完全な」関数とメソッドに言及しています。インライン関数やラムダ式ではなく、 /は通常、周囲のコードの一部であり、そのフロー内に収まる必要があります。