誰もが理解できるコードを書く方法は?







翻訳者から:コードの読みやすさと「プログラマーの共感」の意味に関するCamil Lelonekの記事 公開しました。



あなたは誰があなたのコードを見るのか疑問に思ったことはありますか? 他の人にとってどれほど難しいのでしょうか? その可読性を決定しようとしましたか?

「どんな馬鹿でも機械が理解できるコードを書くことができます。 しかし、優れたプログラマーだけが、人々が理解するコードを書いています」とマーティン・ファウラーは言います。
時々、コードスニペットを見ると、プログラマー間の共感の存在に対する信頼を失います。 私が話していることを理解する必要があります-私たちはそれぞれ、ひどく書かれており、実際には読めないコードに出くわしたからです。

Skillboxの推奨事項: 2年間の実践コース「私はPRO Web開発者です。



「Habr」の読者には、「Habr」プロモーションコードを使用してSkillboxコースに登録すると10,000ルーブルの割引があります。
私は最近このようなものを見ました:



defmodule Util.Combinators do def then(a, b) do fn data -> b.(a.(data)) end end def a ~> b, do: a |> then(b) end
      
      





原則として、ここではすべてが問題ありません。誰かが空想を持っているだけの場合もあれば、コードの作成者が数学的な背景を持っている場合もあります。 このコードを書き直したくはありませんでしたが、無意識のうちに、ここで何かが間違っているように思われました。 「それを改善し、異なる方法で策定する方法が必要です。 すべてがどのように機能するかを確認します。」 かなり早く、私はこれを見つけました:



 import Util.{Reset, Combinators} # ... conn = conn!() Benchee.run( # ... time: 40, warmup: 10, inputs: inputs, before_scenario: do_reset!(conn) ~> init, formatter_options: %{console: %{extended_statistics: true}} )
      
      





うーん、〜>がインポートされているだけでなく、conn!/ 0およびdo_reset!/ 1関数もインポートされているようです。 では、Resetモジュールを見てみましょう。



 defmodule Util.Reset do alias EventStore.{Config, Storage.Initializer} def conn! do {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() conn end def do_reset!(conn) do fn data -> Initializer.reset!(conn) data end end end
      
      





conn!については、このサイトを簡単にする方法がいくつかあります。 それにもかかわらず、この点を熟考しても意味がありません。 むしろdo_resetに焦点を当てたい!/ 1。 この関数は、引数を返し、Initializerのリセットを実行する関数を返します。 また、名前自体は非常に複雑です。







コードをリバースエンジニアリングすることにしました。 bencheeのドキュメントによると、before_scenarioは引数としてスクリプト入力を取ります。 戻り値は、次のステップの入力になります。 著者がおそらく意味するものは次のとおりです。





一般に、すべてが明確であり、そのようなコードは簡単に記述できます。 リファクタリングでは、init関数を表示または変更しないことに注意してください。これはここではあまり重要ではありません。



最初のステップは、暗黙的なインポートの代わりにエイリアスを明示的に使用することです。 Ecto.Queryによってクエリがエレガントになったとしても、コードに表示される「魔法の」機能は好きではありませんでした。 Connectionモジュールは次のようになります。



 defmodule Benchmarks.Util.Connection do alias EventStore.{Config, Storage.Initializer} def init! do with {:ok, conn} = Config.parsed() |> Config.default_postgrex_opts() |> Postgrex.start_link() do conn end end def reset!(conn), do: Initializer.reset!(conn) end
      
      





次に、ドキュメントで提案されているように、「フック」を書くことにしました。



before_scenario:fn入力->入力終了



あとは、データを準備するだけです。 最終結果は次のとおりです。



 alias Benchmarks.Util.Connection conn = Connection.init!() # ... Benchee.run( inputs: inputs, before_scenario: fn inputs -> Connection.reset!(conn) init.(inputs) end, formatter_options: %{console: %{extended_statistics: true}} ) Connection.reset!(conn)
      
      





このコードは完璧ですか? おそらくまだです。 しかし、理解しやすいですか? そう願っています。 すぐにできますか? 間違いなくはい。



問題は何ですか?



著者に解決策を提案したとき、「クール」と聞きました。 しかし、私はこれ以上期待していませんでした。



問題は、プライマリコードが機能したことです。 リファクタリングの必要性について考えさせられた唯一のことは、コードの構造が複雑すぎて読みにくいことです。



他の開発者にコードを読めるように説得するには、説得力のある何かが必要です。 そして、「あいまいだからコードをやり直すことにした」という議論は受け入れられず、答えは「それはあなたがただの悪い開発者であることを意味します。どうすればいいですか?」







これは(非)管理上の問題です



ビジネスが従業員からの結果を期待していることに誰も驚かない。 そして、彼らがより早く受け取られるほど、より良いです。 マネージャーは通常、期限、予算、速度に関してソフトウェアとその作成を評価します。 これが悪いと言っているのではなく、「うまく機能する」高品質のコードがない理由を説明しようとしています。 実際、マネージャーは美しさと読みやすさにあまり興味がなく、販売、低コスト、迅速な結果が必要です。



プログラマーにプレッシャーがかかると、彼らは解決策を模索します。 ほとんどの場合、解決策は「作業コード」を作成し、その中に「クランチ」が多数存在する可能性があることです。 コードを将来維持する必要があるという考えなしに作成されます。 また、エレガントなコードをすばやく作成することは非常に困難です。 プログラマーがどれだけ経験があるかは関係ありません。仕事が時間的なプレッシャーの下で行われるとき、誰も美について考えません。



この状況は、(動作しているとしても)悪いコードが近い将来にそのメンテナンスのコストを増加させると脅すことをマネージャーに確信させることによってのみ解決できます。 バグの修正と新機能の追加、レビュー、技術文書の作成-品質の低いコードの場合、これはすべてエレガントで合理的な状況よりもはるかに時間がかかります。







共感の重要な役割



ソフトウェアを開発している場合、それは他の人を対象としていることを理解しており、開発はチームで行われます。 ここで共感は非常に重要です。



あなたのコードは独特のコミュニケーション形式です。 将来のソフトウェアのアーキテクチャを開発するプロセスでは、コードとやり取りする人について考える必要があります。



「プログラマーの共感」は、締め切りが迫っており、マネージャーが絶えず「つぶれている」場合でも、よりクリーンで合理的なコードを作成するのに役立ちます。 他の誰かの読めないコードを解析するのがどのようなものかを理解するのに役立ちます。これは理解するのが非常に困難です。







結論として



私は最近、Elixirでコードを書きました:



 result = calculate_results() Connection.close(conn) result
      
      





次に、このコードを書き換えることができるRubyタップメソッドを考えました。



 calculate_result().tap do Connection.close(conn) end
      
      





私見、中間変数の結果なしでこれを行う方が良いでしょう。 私はこれをどのように行うことができるかを考え、次の結論に達しました。



 with result = calculate_results(), Connection.close(conn), do: result
      
      





結果は同じになります。 しかし、withを使用すると、このコードを学習している人に問題を引き起こす可能性があります。通常の場合、withは異なる方法で使用されるためです。



したがって、私は他の人を犠牲にして自分自身を良くしないように、すべてをそのままにしておくことにしました。 計り知れないほどコードを複雑にすることなく、同じことをするようお願いします。 エキゾチックな機能や変数を紹介する前に、同僚がレビューでそれらを理解していない可能性があることに注意してください。



一般に、次の原則を使用することをお勧めします。「あなたのコードを書くときは、その他について考えてください。」




All Articles