予期せぬ複雑さに備えて計画を立てるか、それに圧倒されるか

複雑さ
世界は信じられないほど複雑であり、ソフトウェア作成の技術ほどそれが明らかな場所はありません。 ソフトウェアは、社会構造、ビジネス上の欲求、限られた知識の間の複雑な相互作用の結果です。 これらの相互作用の複雑さは、必然的にコード構造に現れます。 エンジニアとしての私たちの仕事は、これを管理し、時間の経過とともに増大する複雑さに対処できるように抽象化を準備することです。 この点で最も重要なテクニックは、ロジックに溺れることなく、より複雑になるためのスペースを確保することです。複雑さは、その量が毒になります。
これが、ソフトウェア作成に関する最も一般的なアドバイスの XNUMX つが、ロジックを多数の小規模で焦点を絞ったメソッドに分割した、小規模で焦点を絞ったクラスを多数作成することである理由です。 このスタイルで書かれたソフトウェアは、個々の部分が複雑に成長する余地を提供します。 Rescale 開発チームでは、成長するメソッドやクラスからロジックを分離する時期は、開発者が通常考えているよりもはるかに早いことがわかりました。 私たちは、クラスとメソッドの最初と XNUMX 番目のバージョンを作成するときに、抽象化を取り出し始めることを好みます。
例で説明すると、私たちは最近、ワークフロー (転送するファイル、実行する分析、変数定義) を記述したサードパーティの XML ファイルを解析するコードを作成していました。 当初、私たちの解析コードは比較的単純でしたが、その主な理由は、解析する必要があるすべてのことを知らなかったためです。 私たちは入力ファイルと変数を解析することから始めました。 これらのそれぞれは xml ノードによって表され、各 xml ノードは type という名前の属性でその型を示し、さらに型に依存する属性で重要な値を示します。
初期の解析コードは次のようになります。

コレクション inputFileNames = getNodeStream() .filter(n -> n.getAttributes() .getNamedItem(“type”) .getNodeValue().equals(“inputFileType”)) .map(n -> n.getAttributes() .getNamedItem( “ファイル名”) .getNodeValue()) .collect(Collectors.toList()); コレクション inputVariableNames = getNodeStream() .filter(n -> n.getAttributes() .getNamedItem(“type”) .getNodeValue().equals(“inputVariableType”)) .map(n -> n.getAttributes() .getNamedItem( “変数名”) .getNodeValue()) .collect(Collectors.toList());

ヘルパーメソッド getNodeStream 上記は、を変換します ノードリスト 文書から 流れ 操作を容易にするためです。
このコードについて注意すべき点が XNUMX つあります。 定数の代わりにマジック文字列を使用する、コードを複製して属性値を抽出します。 これらの単純なリファクタリングを適用すると、解析コードは実装の詳細が少なくなり、その意図をより正確に読み取ることができます。

コレクション inputFileNames = getNodeStream() .filter(n -> INPUT_FILE_TYPE.equals(getAttribute(n, TYPE))) .map(n -> getAttribute(n, FILE_NAME)) .collect(Collectors.toList()); コレクション inputVariableNames = getNodeStream() .filter(n -> INPUT_VARIABLE_TYPE.equals(getAttribute(n, TYPE))) .map(n -> getAttribute(n, VARIABLE_NAME)) .collect(Collectors.toList());

これは、複雑化するコードの準備にベスト プラクティスがどのように役立つかを示す最初の例です。 表面的には、大したことはできていないように見えますが、コードを書くことで、私たちの考えに近いものになります。 意味する、コンピューターのことよりも、 ありません、新しい追加を記述するときに頭の中に保持するコンテキストが少なくなるため、このコードにさらに複雑さを追加しやすくなりました。
これらの文字列のコレクションを解析することがこの XML で行ったすべてである場合は、このコードをそのままにしておいても問題ありません。 しかし、これがソフトウェアであるため、事態はさらに複雑になります。 XNUMX 番目または XNUMX 番目の型/属性解析ペアを書き出した後、いくつかの列挙型を抽出することにしました。

public enum NodeAttribute { TYPE(“タイプ”), FILE_NAME(“ファイル名”), VARIABLE_NAME(“変数名”), … private Final String attrName; private NodeAttribute(String attrName) { this.attrName = attrName; public String getValue(Node node) { return Node.getAttributes() .getNamedItem(this.attrName) .getNodeValue(); } public enum NodeType { INPUT_FILE(“inputFile”), INPUT_VARIABLE(“inputVariable”), OUTPUT_VARIABLE(“outputVariable”), … プライベート最終文字列値; private NodeType(文字列値) { this.value = 値; public booleanmatchs(Nodenode) { return NodeAttributes.TYPE.getValue(node).equals(this.value); } }

解析コードは次のようになります。

コレクション inputFileNames = getNodeStream() .filter(NodeType.INPUT_FILE::matches) .map(NodeAttribute.FILE_NAME::getValue) .collect(Collectors.toList()); コレクション inputVariableNames = getNodeStream() .filter(NodeType.INPUT_VARIABLE::matches) .map(NodeAttribute.VARIABLE_NAME::getValue) .collect(Collectors.toList()); …

ここでロジックを抽出するのはやりすぎのように思えるかもしれませんが、列挙型が再利用のためにこれらの定数を定義するための中心的な場所を提供するため、そうすることに動機を与えました。 私たちがすぐに知ったもう XNUMX つの利点は、 さまざまな部分がより複雑になるためのスペース。  XML ノードから属性を取り出すだけなのに、どうしてこれがさらに複雑になるのかと考えているかもしれません。 私たちは確かに、そんなことになるとは、あるいはできるとは思っていませんでした。
しかし、このサードパーティ XML では、一部のノードは filename という名前の属性でファイルを参照し、一部のノードは fileName という名前の属性でファイルを参照していることがわかります。 これはプログラマーを呪わせる類のものですが、幸いなことに、私たちはこれを簡単に処理する準備ができていました。

public enum NodeAttribute { TYPE(“タイプ”), FILE_NAME(“ファイル名”, “ファイル名”), VARIABLE_NAME(“変数名”), … private Final String[] attrNames; private NodeAttribute(String... attrNames) { this.attrNames = attrNames; public String getValue(Node node) { return Arrays.stream(this.AttrNames) .map(attr -> node.getAttributes().getNamedItem(attr)) .filter(attr -> attr != null) .findFirst() .orElseThrow(() -> new NoSuchElementException( "Node: " + node.getNodeValue() + " という名前の属性がありませんでした: " + Arrays.toString(mAttrNames) )); }

他の解析コードは変更する必要がありませんでした。 文字列定数を使用し続けた場合、ファイル名または fileName をチェックするために解析コード全体で多くの更新を行うか、ファイルを参照するノード用の特別なメソッドを記述する必要がありました。 if/else ロジックを使用するとコードがさらに複雑になるでしょう。 ただし、早い段階で抽象化したため、このロジックを配置する場所ができました。 属性の大文字と小文字の違いがこのように異なるとは予想していなかったということを繰り返しておきますが、まさにそれが重要です。 期待する コードはさまざまな方法で複雑になります 期待しないでください.
一部のノードが filename を使用し、他のノードが fileName を使用するのはなぜですか? XNUMX 人の異なる人が異なるノードのシリアル化に取り組んでおり、もう一方が選択した大文字スキームを知らなかったと推測できます。 おそらく口頭でコミュニケーションをとり、属性として「ファイル名」を決めたのでしょうが、ある人はキャメルケースを使用していました。 あるいは、ノードを次々と処理し、どのような大文字スキームが使用されていたかを忘れてしまったのかもしれません。
いずれにせよ、サードパーティの開発チームの社会構造の複雑さは、この属性名の違いに現れており、コードベースにも反映されています。 そのような複雑さに備え、適切な構造で対処できるようにするのが私たちの仕事です。

著者

  • Adam McKenzie

    アダムは CTO として、HPC チームとカスタマー サクセス チームの管理を担当しています。 アダムはボーイングでキャリアをスタートし、787 年間 XNUMX に取り組み、主翼の設計、分析、最適化を行う構造およびソフトウェア エンジニアリング プロジェクトを管理しました。 アダムはオレゴン州立大学で機械工学の学士号を優秀な成績で取得しています。

類似の投稿