コードが賞賛されるのはいつですか?





理想的なコードのトピックは、経験豊富なプログラマーの間でしばしば論争を引き起こします。 Parallels RAS開発ディレクターのIgor Marnatの意見を聞くのは、さらに面白かったです。 カットの下で、トピックに関する彼の著者の意見。 お楽しみください!







はじめに、なぜこの短い記事を書くことにしたのかという疑問についてお話したいと思います。 それを書く前に、タイトルからいくつかの開発者に質問をしました。 私はほとんどの人と5年以上働いており、少々少なかったが、私は彼らのプロ意識と無条件の経験を信頼している。 10年以上にわたる産業開発のすべての経験、誰もがロシアおよび国際企業、ソフトウェアメーカーで働いています。



一部の同僚は答えるのが難しいと感じた(一部の人々はまだ考えている)、他の同僚は一度に1つか2つの例を呼んだ。 例を挙げた人に、私は明確な質問をしました-「実際、この賞賛の原因は何ですか?」 答えは、私の小さな研究の次の段階の結果と一致していました。 この質問に対する答えを、記事のタイトルに近いさまざまな形式でWebで検索しました。 すべての記事は、私の仲間が答えたのとほぼ同じ方法で答えました。



開発者の回答、および見つかった記事の文言は、コードの読みやすさと構造、論理構造の優雅さ、最新のプログラミング言語のすべての機能の使用、および特定のスタイルに従うことに関連しています。



私が「神のコード」について自分自身に質問すると、潜在意識から即座に答えが浮上しました。 すぐに(10年以上前)一緒に作業していた2つのコード例を考えましたが、それでも感心と敬意を感じます。 それぞれの賞賛の理由を検討した後、以下で説明するいくつかの基準を策定しました。 最初の例については詳しく説明しますが、2番目の例をより詳細に分析したいと思います。 ところで、さまざまな程度で、これらの基準はすべて、Steve McConnellによる各開発者のハンドブック「 Perfect Code 」で考慮されていますが、この記事は著しく短くなっています。



90年代の例



最初に言及する例は、v42bisモデムプロトコルの実装に関するものです。 このプロトコルは、80年代後半から90年代前半に開発されました。 プロトコルの開発者によって具体化された興味深いアイデアは、不安定な(電話)通信回線を介した伝送中の情報のストリーム圧縮の実装です。 ストリーム圧縮とファイル圧縮の違いは基本です。 ファイルを圧縮する場合、アーカイバはデータセットを完全に分析し、データの圧縮およびエンコードの最適なアプローチを決定し、データやメタデータの損失を心配することなくデータ全体をファイルに書き込むことができます。 解凍すると、データセットは再び完全にアクセス可能になり、チェックサムによって整合性が確保されます。 インライン圧縮では、アーカイバは小さなデータウィンドウにのみアクセスでき、データ損失がないという保証はありません。接続を再インストールし、圧縮プロセスを初期化する必要があります。



アルゴリズムの作成者はエレガントなソリューションを発見しましたが、その説明には文字通り数ページかかります 。 何年も経ちましたが、アルゴリズムの開発者によって提案されたアプローチの美しさと優雅さに今でも感銘を受けています。



この例では、コード自体を参照しているのではなく、アルゴリズムを参照しているため、詳細については説明しません。



Linuxはあらゆるものの頭です!



完全なコードの2番目の例をより詳細に分析したいと思います。 これはLinuxカーネルコードです。 執筆時点で、 上位500台から500台のスーパーコンピューターの動作を制御するコード。これは、世界の2番目の電話で機能し、インターネット上のほとんどのサーバーを制御するコードです。



たとえば、Linuxカーネルの memory.cファイルについて考えてみましょう。これは、メモリ管理サブシステムを指します。



1.ソースは読みやすいです。 これらは、わかりやすく、混乱しにくい非常にシンプルなスタイルを使用して記述されています。 大文字はプリプロセッサディレクティブとマクロにのみ使用され、その他はすべて小文字で記述され、名前の単語はアンダースコアで区切られます。 これはおそらく、スタイルがまったくないことを除いて、おそらく最も簡単なコーディングスタイルです。 同時に、コードは完全に読み取り可能です。 インデントとコメントのアプローチは、カーネルファイルのどの部分からでも見ることができます。次に例を示します。



static void tlb_remove_table_one(void *table) {     /*      * This isn't an RCU grace period and hence the page-tables cannot be      * assumed to be actually RCU-freed.      *      * It is however sufficient for software page-table walkers that rely on      * IRQ disabling. See the comment near struct mmu_table_batch.      */     smp_call_function(tlb_remove_table_smp_sync, NULL, 1);     __tlb_remove_table(table); }
      
      







2.コードにはあまり多くのコメントはありませんが、通常は有用なコメントが役立ちます。 原則として、彼らはコードからすでに明らかなアクション(役に立たないコメントの典型的な例は「cnt ++; //カウンターのインクリメント」)ではなく、このアクションのコンテキスト-ここで何が行われ、なぜそれが行われ、なぜここでは、どのような前提で使用されているか、コード内の他のどの場所で接続されているかを示します。 例:



 /** * tlb_gather_mmu - initialize an mmu_gather structure for page-table tear-down * @tlb: the mmu_gather structure to initialize * @mm: the mm_struct of the target address space * @start: start of the region that will be removed from the page-table * @end: end of the region that will be removed from the page-table * * Called to initialize an (on-stack) mmu_gather structure for page-table * tear-down from @mm. The @start and @end are set to 0 and -1 * respectively when @mm is without users and we're going to destroy * the full address space (exit/execve). */ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm,            unsigned long start, unsigned long end)
      
      







カーネルでのコメントのもう1つの用途は、変更の履歴を記述することです。通常はファイルの先頭にあります。 カーネルの歴史は30年近くあり、いくつかの場所を読むのは興味深いものです。ストーリーの一部を感じます。



 /* * demand-loading started 01.12.91 - seems it is high on the list of * things wanted, and it should be easy to implement. - Linus */ /* * Ok, demand-loading was easy, shared pages a little bit tricker. Shared * pages started 02.12.91, seems to work. - Linus. * * Tested sharing by executing about 30 /bin/sh: under the old kernel it * would have taken more than the 6M I have free, but it worked well as * far as I could see. * * Also corrected some "invalidate()"s - I wasn't doing enough of them. */
      
      







3.カーネルコードは特別なマクロを使用してデータを検証します。 また、コードが機能するコンテキストを確認するためにも使用されます。 これらのマクロの機能は標準のアサートに似ていますが、条件が真の場合に実行されるアクションを開発者がオーバーライドできるという違いがあります。 カーネルでのデータ処理の一般的なアプローチ-ユーザー空間からのすべてがチェックされ、エラーのあるデータの場合、対応する値が返されます。 この場合、WARN_ONを使用して、カーネルログにエントリを発行できます。 BUG_ONは通常、新しいコードをデバッグし、新しいアーキテクチャでカーネルを起動するときに非常に役立ちます。



BUG_ONマクロは通常、レジスタとスタックの内容を出力し、対応する呼び出しが発生したコンテキストでシステム全体またはプロセスを停止します。 WARN_ONマクロは、条件が真の場合にカーネルログにメッセージを発行するだけです。 また、マクロWARN_ON_ONCEと他の多くのマクロがあり、その機能は名前から明らかです。



 void unmap_page_range(struct mmu_gather *tlb, ….     unsigned long next;    BUG_ON(addr >= end);    tlb_start_vma(tlb, vma); int apply_to_page_range(struct mm_struct *mm, unsigned long addr, …    unsigned long end = addr + size;    int err;    if (WARN_ON(addr >= end))        return -EINVAL;
      
      







信頼できないソースから取得したデータを使用前にチェックし、「不可能な」状況に対するシステムの応答を提供および決定するアプローチにより、システムのデバッグと操作が大幅に簡素化されます。 このアプローチは、フェイル早期かつ大声での原則の実装と考えることができます。



4.カーネルのすべてのコアコンポーネントは、仮想ファイルシステム/ proc /というシンプルなインターフェイスを介して、ユーザーにステータスに関する情報を提供します。



たとえば、メモリステータス情報は/ proc / meminfoファイルで利用可能です



 user@parallels-vm:/home/user$ cat /proc/meminfo MemTotal:    2041480 kB MemFree:      65508 kB MemAvailable:   187600 kB Buffers:      14040 kB Cached:      246260 kB SwapCached:    19688 kB Active:     1348656 kB Inactive:     477244 kB Active(anon):  1201124 kB Inactive(anon):  387600 kB Active(file):   147532 kB Inactive(file):  89644 kB ….
      
      







上記の情報は、メモリ管理サブシステムのいくつかのソースファイルで収集および処理されます。 したがって、最初のMemTotalフィールドはsysinfo構造体のtotalramフィールドの値であり、 page_alloc.cファイルのsi_meminfo関数で満たされています



明らかに、このような情報へのユーザーアクセスの収集、保存、および提供を整理するには、開発者の努力とシステムのオーバーヘッドが必要です。 同時に、そのようなデータへの便利で簡単なアクセスの利点は、開発プロセスとコードの操作の両方で非常に貴重です。



ほとんどすべてのシステムの開発は、コードとデータの内部状態に関する情報を収集して提供するシステムから始める必要があります。 これは、開発とテストのプロセス、およびその後の運用に大いに役立ちます。



Linusが言ったように 、「悪いプログラマーはコードを心配しています。 優れたプログラマーは、データ構造とその関係を心配しています。」



5.すべてのコードは、コミットする前に複数の開発者によって読み取られ、議論されます。 ソースコード変更の履歴が記録され、利用可能です。 任意の行の変更は、その発生まで追跡できます。変更内容、変更者、変更者、変更時期、理由、開発者によって議論された問題。 たとえば、memory.cコードのhttps://github.com/torvalds/linux/commit/1b2de5d039c883c9d44ae5b2b6eca4ff9bd82dac#diff-983ac52fa16631c1e1dfa28fc593d2efの変更はhttps://bugzsh_b.bg?bg_g.bg_g.bg_b.bg_g.bg_b.bg_g.bg_b.bg_g.bg_g.bg_b。コードの小さな最適化が行われました(メモリが既に書き込み保護されている場合、メモリ書き込み保護を有効にする呼び出しは発生しません)。



コードを使用して作業する開発者は、自分が行った変更によってどのシナリオが影響を受ける可能性があるかを理解するために、このコードのコンテキスト、コードが作成された前提、コードが変更された時期、および変更時期を理解することが常に重要です。



6.カーネルコードライフサイクルのすべての重要な要素は文書 化されており、コーディングスタイルから始まり、安定したカーネルバージョンのリリースの内容とスケジュールで終わります 。 カーネルコードをある容量または別の容量で操作したいすべての開発者とユーザーは、このために必要なすべての情報を持っています。



これらの瞬間は私にとって重要であるように見えました。基本的に、それらはコアコードに対する私の熱意を決定しました。 明らかに、リストは非常に短く、拡張できます。 しかし、私の意見では、上記のポイントは、このコードを使用する開発者の観点から、ソースコードのライフサイクルの重要な側面に関連しています。



結論として私が言いたいこと。 コア開発者は賢く経験豊富で、成功しています。 数十億のLinuxデバイスで実証済み



カーネル開発者として、ベストプラクティスを使用し、Code Completeをお読みください!



Z.Y. ところで、個人的に理想的なコードの基準は何ですか? コメントであなたの考えを共有してください。



All Articles