抽象化を見る能力





私の息子は、多くの男の子のように、車が好きです。 さらに、彼らがより大きくてより珍しいほど、彼らはより好かれます。 通りを歩いているときに、レッカー車や除雪車が通り過ぎると、彼は常に私の手を引いて、興味のある物体を指して、「お父さん、お兄さん!」と言います。 彼はそう言っています。なぜなら彼は1歳であり、上記の2つの単語が語彙の40%を占めているからです。 それにもかかわらず、一般的に、アイデアは明確です-車に注意を払うこと。 8-10歳の子供がどのように同じことを同僚に言うか考えてみましょう。 「わあ、なんてかっこいい車だ!」 考え方は同じですが、注意してください-2つではなく6つの単語。 そして最後に、30代後半の男性が同じことをどのように言うか想像してみてください。「ねえ、これは454馬力のV4エンジンと7速オートマチックトランスミッションを備えた2008年フェラーリカリフォルニアです。 3.9秒で数百に加速します!」 はい、すでに詳細がありますが、あなたが自動車整備士またはフェラーリのファンでない場合、ほとんどの場合、必要ではなく、重要ではありません。 主なアイデアは、「すごい、かっこいい車だ!」や「お父さん、br-r!」と同じです。 しかし、すでに30語で表現されています。



「興味深い車」という抽象化が詳細とニュアンスで大きくなりすぎており、理解と分析と応答のためにテキストと時間の中でかなり多くのスペースを占めるようになったことに気づきましたか? 同じことがプログラムコードでも発生します。



私たちは何について話しているのですか



私の意見では、優れたプログラマーの主な特徴は、数学の深い知識ではなく、彼の背後にある100年の経験ではなく、言語やライブラリーの知識ではなく、その他の重要なことの%ではなく、抽象化を見る能力です。 もちろん、見るだけでなく、設計、使用、修正なども行います。 しかし、事実は事実です。 ここで今日人気のある製品(お気に入りのOS、ブラウザー、ゲームなど)の成功は、そのアーキテクチャがどの程度適切に設計されているか、高レベルの部分が低レベルからどの程度分離されているかによって正確に決まります。



「死んだ」プロジェクトを見てください。 プログラマが速度を10%上げることができなかったという事実、または目的のライブラリを固定できなかったために、彼らが死ぬことはほとんどありません。 ほとんどの場合、閉鎖の理由は、「既存のアーキテクチャにより、それ以上の開発が根本的に不可能になる」という精神で定式化されます。 ここに、抽象化を見る際の間違いがあります。 長い間、複数のエンティティが実際に1つである、または複数の表現がある可能性がある、または実際にはサーバーをプルするのはクライアントではなく、プロトコルに拡張する可能性を含めるのがよいと誰かが見なかった-そして、ここで彼は、何年もの結果の雷の後、雷鳴を浴びています。



パターン



プログラミングの現代世界では、「パターン」などがあります。 まあ、あなたは知っている、4つのギャングの本、すべての種類の工場\シングルトン\ラッパー\オブザーバー\ファサード\ブリッジ。 パターンに対するプログラマの態度はあいまいです。 これがすべてであると正当に主張するパターン愛好家のためのキャンプがあります-何十年もの最高のプログラミング経験の真髄、物事をチェックし、速度を落とさず、ベストプラクティスを使用します。 そして、過度の複雑さ(1つのアイデアを実装するために3〜5段階-平凡なパターンでは非常に典型的です)を非難するパターンの反対者の陣営があり、彼らは学習パターンは学校の詰め込みのようなものであると言います-原因、結果を理解せずに何かを学ぶとき特にターゲットを絞った使用オプション。



ここでのポイントは、パターンと抽象化の関係にあるように思えます。 一部のパターンは全体的であり、単一の概念を明確に説明しています。 それはそれ自身でカプセル化され、理解され、見られ、実装されます。 クラスが1つでも5つでも10でも構いません-それらが外部環境に依存しない特定のエンティティを形成する場合は、別のモジュールに配置し、シンプルなインターフェースで使用できます-これは良いパターンです。



他のパターンは明白なゴミです。 これらの2つのクラスが3番目にあり、4番目から継承され、5番目のクラスのメソッドを呼び出すという理由だけで、抽象化は作成されません。 おそらく何らかの形でコードを高速化するか、何らかの言語制限をバイパスするかもしれませんが、全体的なアイデアを形成するわけではありません。 理解するのは難しく、覚えることは不可能であり、プログラマーの合理的な脳に正しい怒りを引き起こします。 文を形成しない単語のランダムなセットのようなものが読者にあります。 高速ハッチにもかかわらずパジャマの甘いジャンプムーン?



ツール



残念ながら、最新の開発ツールは、プロジェクトの抽象化を確認するための優れた自動化ツールを提供していません。 はい、コードでインターフェイスまたは抽象クラスを見ることができますが、これが実際の抽象化を構成するという事実ではありません。 ロジックの特定の層には数十個のインターフェイスを含めることができ、他の層には1つのクラスのみを含めることができます(その1つは他の何かの単なるラッパーです)。 IDEを使用してクラス、メソッド、変数を確認できますが、プロジェクトがレイヤーに実際に分離されていることは確認できません。 すべてはプログラマの良心に残ります。 幸いなことに、今日はコードを個別のモジュールで作成する機会があり、名前空間、インターフェイス、実用的なリファクタリングのヒント、およびその実装のためのツールがあります。 別々のモジュールにうまく分割されたコードを書くことは可能です。 そして、これは高速なコードを書くよりも重要です。 もちろん、「モジュラーコード」は「完全なコード」と100%一致するわけではありませんが、これに非常に近いものです。



悪いコードの例



数年前、Notepad ++テキストエディタの人気がピークに達しました。 何千万ものダウンロード、素敵なミニマルなインターフェース、プラグイン。 始まりは非常に良く、何も悪いことはありませんでした。 過去数年にわたって、このテキストエディターは吹き飛ばされ、実際にその開発が行き詰っていました。 これが彼のダウンロードのグラフです。





その理由は何ですか? 私はそれらをすべて呼び出すとは思いませんが、ここに一つあります。 ソースから1つのファイルを見てみましょう。

NppBigSwitch.cpp



このコードの一部を詳しく見ていきます。



「変数名」の概念の誤解。
Macro m = _macro;
      
      





変数名はプロセッサのレジスタ名ではありません。 変数名の主なタスクは、メモリ内のセルのアドレスを指定することではなく、セルに含まれるデータの種類を説明することです。 アドレス指定のためだけに、最初に一度メモリ内の配列を選択し、ポインタで実行することができます。 コードの理解を簡素化する抽象化は変数の名前です。 ここで見るように、単純化はしません。



バージョン管理システムの誤解
 /* case NPPM_ADDREBAR : { if (!lParam) return FALSE; _rebarTop.addBand((REBARBANDINFO*)lParam, false); return TRUE; } case NPPM_UPDATEREBAR : { if (!lParam || wParam < REBAR_BAR_EXTERNAL) return FALSE; _rebarTop.reNew((int)wParam, (REBARBANDINFO*)lParam); return TRUE; } case NPPM_REMOVEREBAR : { if (wParam < REBAR_BAR_EXTERNAL) return FALSE; _rebarTop.removeBand((int)wParam); return TRUE; } */
      
      





古い不要なコードは、削除された理由についての別のコミットでコメントを付けて削除する必要があります。 これがまだ機能を完了していない場合-別のブランチに配置します。 プロジェクトファイルにコメントアウトされたコードの山を保持することは、バージョン管理システムの概念を理解しないことを意味します。



プログラムの速度がより重要であり、プログラマの速度がどこであるかについての誤解
 LRESULT Notepad_plus::process(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) { ... //  1800   ... }
      
      





1つの機能にこれほどのサイズはありませんか? 著者は、個別の関数を使用して個々のコードを呼び出すことにより、数十ナノ秒の節約を試みたに違いありません。 よくできました、保存しました。 そして、私はそれを掘り下げるのがうんざりするコードを手に入れたので、あなたは理解と修正に多くの時間を費やす必要があります。



抽象化の誤解「フラグ」、「定数」、「マジックナンバー」。
 nmdlg->Items[i] = 0xFFFFFFFF; // indicate file was closed ... if ((lParam == 1) || (lParam == 2)) ... long view = MAIN_VIEW; view <<= 30; return view|i;
      
      





0xFFFFFFFF、1、2、および30とは何ですか? そうそう、0xFFFFFFFF-ファイルが閉じられたことを意味します。 なんて素晴らしいコメント、それがどれほど明確か! 数字をコードにドロップするだけで非常に高速で便利だったようです。結局、コンパイルされます。 説明する時間はありません、さらに進んでいきましょう。



エンコーディングの抽象化の欠如
  case COPYDATA_FILENAMESA : { char *fileNamesA = (char *)pCopyData->lpData; CmdLineParams & cmdLineParams = pNppParam->getCmdLineParams(); #ifdef UNICODE WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); const wchar_t *fileNamesW = wmc->char2wchar(fileNamesA, CP_ACP); loadCommandlineParams(fileNamesW, &cmdLineParams); #else loadCommandlineParams(fileNamesA, &cmdLineParams); #endif break; } case COPYDATA_FILENAMESW : { wchar_t *fileNamesW = (wchar_t *)pCopyData->lpData; CmdLineParams & cmdLineParams = pNppParam->getCmdLineParams(); #ifdef UNICODE loadCommandlineParams(fileNamesW, &cmdLineParams); #else WcharMbcsConvertor *wmc = WcharMbcsConvertor::getInstance(); const char *fileNamesA = wmc->wchar2char(fileNamesW, CP_ACP); loadCommandlineParams(fileNamesA, &cmdLineParams); #endif break; } } return TRUE; }
      
      





プログラムがUnicodeでコンパイルされているかどうかに関係なく、あらゆる場所で、現時点で不要なものをプログラマの脳にロードする構造が見られます。 多くの関数がUnicodeバージョンと非Unicodeバージョンを持っているWin32 APIでさえ、毎回何を呼び出すかを考えず、「MessageBox」と書くだけで、マクロのおかげでMessageBoxAまたはMessageBoxWに置き換えられます。 これにより、このレベルを上回り、この詳細を覚える必要性から脳を切り離すことができます。 Notepad ++の作成者にとって、この方法は簡単すぎるようです。



オペレーティングシステムUIプリミティブ上の抽象化層の欠如
 ::MoveWindow(_rebarTop.getHSelf(), 0, 0, rc.right, _rebarTop.getHeight(), TRUE); ... ::SendMessage(_statusBar.getHSelf(), WM_SIZE, wParam, lParam);
      
      





インターフェイス要素を使用してウィンドウを移動したり、非表示にしたり、他のアクションを実行したりする必要があるたびに、Win32 API関数は直接動作します。 インターフェイスライブラリ、ラッパー、クラスはありません-何もありません。 その結果、冗長で重複したコードの束、他のオペレーティングシステムへの絶対的な移植性、Win32 APIのすべての不利な点がコード内にあります。 さらに、著者は、製品の利点としてそのようなアプローチを公開し、追加のコンポーネントはありません! これはひどいです。 (せいぜい)生産性向上の100分の1-とソースの地獄。



上記のすべての結果として、Notepad ++開発は悲惨なほど複雑で遅くなります。 いくつかの重要なバグを修正する私のパッチは、他のほぼ200のパッチと一緒に、すでに半年間「検討中」のリストに残っています。 もちろん、著者は時々それらのいくつかを受け入れますが、そのようなコードベースでこれを迅速に行うことは絶対に不可能であることをあなたはあなた自身が理解しています。 かつて私にとっては良い編集者のように思われた編集者には非常に申し訳ありませんが、あなたは自分の目で見てください-死は避けられません。



良いコード例



Qtのような人気のあるライブラリをご存知かもしれません-最近Habréで多くのことが書かれています。 繰り返しますが、私はその成功のすべての理由を最もよく知っているとは言いませんが、ここにその1つがあります。 ライブラリ全体は、抽象化の美しいコアに基づいて構築されています。プラットフォーム、ネットワーク、OSインターフェイス要素、エンコーディング、およびほぼすべてのものから抽象化されています。 Qtコンポーネントを見ると、その仕組みを理解するために深く掘り下げる必要はありません。 そして、これはすべて優れたドキュメントによるものではなく、ライブラリ自体のコードによるものです。



Qtライブラリヘッダーファイルの1つを見てみましょう。



qpdfwriter.h
 #ifndef QPDFWRITER_H #define QPDFWRITER_H #include <QtCore/qobject.h> #include <QtGui/qpagedpaintdevice.h> QT_BEGIN_NAMESPACE class QIODevice; class QPdfWriterPrivate; class Q_GUI_EXPORT QPdfWriter : public QObject, public QPagedPaintDevice { Q_OBJECT public: explicit QPdfWriter(const QString &filename); explicit QPdfWriter(QIODevice *device); ~QPdfWriter(); QString title() const; void setTitle(const QString &title); QString creator() const; void setCreator(const QString &creator); bool newPage(); void setPageSize(PageSize size); void setPageSizeMM(const QSizeF &size); void setMargins(const Margins &m); protected: QPaintEngine *paintEngine() const; int metric(PaintDeviceMetric id) const; private: Q_DISABLE_COPY(QPdfWriter) Q_DECLARE_PRIVATE(QPdfWriter) }; QT_END_NAMESPACE #endif
      
      







あなたになじみがないかもしれないマクロに驚かないでください-それはそれらについてではありません。 別のことに注意を向けたいと思います。 このクラスにはプライベートプロパティがないことに注意してください。 そして、他のクラスQtではほとんどありません-どちらでも。 むしろ、実際には、それぞれにプライベート変数が1つだけあります。これは、すべてのプロパティとプライベートメソッドの一部が既に配置されているサブクラスへのQ_DECLARE_PRIVATEマクロを介して暗黙的に宣言されたポインターです。 これは、バイナリ互換性を確保するための公式の説明に従って行われます-Qtのすべてのバージョンのこのクラスは同じサイズであり(プラットフォーム内の1つのポインターの記憶領域は一定であるため)、モジュール間で安全にやり取りできることがわかりますそこに何かセグメンテーション違反。



実際、私にとって、この決定の全体的な魅力は異なります。 見て-ヘッダーファイルを開くと、何が見えますか? パブリックメソッドのみ。 このヘッダーファイル(および実際にはQtライブラリ)を見るのはこれが初めてですが、このクラスが何であり、どのように使用するかは既に理解しているでしょうか? すべての内部はプライベートサブクラスで優雅に隠されています。 残念ながら、古典的なC ++では、プログラマはクラスとその内部の外部ユーザーが必要とするものをヘッダーファイルに混在させる必要があります。 しかし、Qtでは、ヘッダーファイルを読み取るときに、Qt開発者からの明確なメッセージが表示されます。「このクラスの内部は必要ありません。 それはあなたのビジネスではなく、どのような方法で、抽象化です-パブリックメソッドを介して使用します。」 そして、それは大丈夫です、気に! すべてのライブラリを同じようなスタイルで見たいです。 必要なものをすぐに見せて、不要なものを非表示にして、長くて厳しいものを探す必要があるようにします。 将来はこのアプローチの背後にあると確信しています。



結論



「リファクタリングを使用する」、「良いパターンを適用する」、「最適化が早すぎる」、「大きな関数を書かない」、「グローバル変数を取得しない」など、プログラマーにとって最も良いヒントは、実際にはより一般的な「抽象化の見方を知る」ヒントからの結論です。 「。



いいプログラミングをしてください。



All Articles