From the blog

Plan for Unexpected Complexity or Be Overwhelmed by It

complexity
The world is incredibly complex, and nowhere is this more evident than the art of writing software. Software is the result of intricate interactions between social structures, business desires, and limited knowledge. The complexity of those interactions inevitably shows up in code structure. Our job as engineers is to manage this, and prepare our abstractions to handle increasing complexity over time. The most important technique in this regard is to make space for pieces of logic to take on more complexity without drowning in it – with complexity, the dose makes the poison.
This is why one of the most common pieces of software writing advice that you’ll hear is to write lots of small, focused classes with logic split up into lots of small, focused methods. Software written in this style provides space for any individual piece to grow in complexity. On the Rescale development team, we’ve found that the time to split off logic from a growing method or class is much earlier than developers typically think. We prefer to start pulling out abstractions as we write first and second versions of classes and methods.
To illustrate with an example, we were recently writing some code to parse third party xml files that described workflows – files to transfer, analyses to run, and variable definitions. At first our parsing code was relatively simple, mostly because we didn’t know everything that we would need to parse. We started by just parsing input files and variables. Each of those are represented by an xml node, and each xml node indicates its type with an attribute named type, furthering indicating important values with attributes dependent on the type.
Some initial parsing code looked like:

Collection inputFileNames = getNodeStream()
  .filter(n -> n.getAttributes()
                .getNamedItem(“type”)
                .getNodeValue().equals(“inputFileType”))
  .map(n -> n.getAttributes()
             .getNamedItem(“filename”)
             .getNodeValue())
  .collect(Collectors.toList());
Collection inputVariableNames = getNodeStream()
  .filter(n -> n.getAttributes()
                .getNamedItem(“type”)
                .getNodeValue().equals(“inputVariableType”))
  .map(n -> n.getAttributes()
             .getNamedItem(“variableName”)
             .getNodeValue())
  .collect(Collectors.toList());

The helper method getNodeStream above converts a NodeList from the document into a stream for ease of manipulation.
There are two things to notice about this code – it uses magic strings instead of constants, and duplicates code to extract attribute values. After applying those simple refactorings, the parsing code is less cluttered with implementation details and reads more closely to its intent:

Collection inputFileNames = getNodeStream()
  .filter(n -> INPUT_FILE_TYPE.equals(getAttribute(n, TYPE)))
  .map(n -> getAttribute(n, FILE_NAME))
  .collect(Collectors.toList());
Collection inputVariableNames = getNodeStream()
  .filter(n -> INPUT_VARIABLE_TYPE.equals(getAttribute(n, TYPE)))
  .map(n -> getAttribute(n, VARIABLE_NAME))
  .collect(Collectors.toList());

This is our first example of how best practices can help prepare code for increasing complexity. On the surface, it doesn’t seem like we’ve done much, but by writing code that’s closer to what we mean, rather than what the computer does, we’ve made it easier to add more complexity to this code because we’ll have less context to keep in our heads as we write new additions.
If parsing out these collections of strings was all we ever did with this xml, it would be fine to leave this code as is. But, this being software, things got more complex. After writing out the third or fourth type/attribute parsing pair, we decided to extract some enums:

public enum NodeAttribute {
  TYPE(“type”),
  FILE_NAME(“filename”),
  VARIABLE_NAME(“variableName”),
  …
 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 final String value;
  private NodeType(String value) {
    this.value = value;
  }
  public boolean matches(Node node) {
    return NodeAttributes.TYPE.getValue(node).equals(this.value);
  }
}

Now the parsing code looks like:

Collection inputFileNames = getNodeStream()
  .filter(NodeType.INPUT_FILE::matches)
  .map(NodeAttribute.FILE_NAME::getValue)
  .collect(Collectors.toList());
Collection inputVariableNames = getNodeStream()
  .filter(NodeType.INPUT_VARIABLE::matches)
  .map(NodeAttribute.VARIABLE_NAME::getValue)
  .collect(Collectors.toList());
…

It seems like it might be overkill to extract logic here, but we were motivated to do so because the enums provide a central location to define these constants for reuse. Another benefit we soon learned of was that they provided space for the different pieces to become more complex.  Now you might be thinking, it’s just pulling out an attribute from an xml node, how could that become more complex? We certainly didn’t think it would or could.
But it turns out that in this third party xml, some nodes reference files with an attribute named filename and some referenced files with an attribute named fileName. This is the kind of thing that makes programmers curse, but luckily we were prepared to handle this with ease:

public enum NodeAttribute {
  TYPE(“type”),
  FILE_NAME(“filename”, “fileName”),
  VARIABLE_NAME(“variableName”),
  …
 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()
            + " didn't have any attributes named: "
            + Arrays.toString(mAttrNames)
       ));
}

None of our other parsing code had to change. If we had kept on using string constants, we would have had to make many updates across the parsing code to check for filename or fileName, or else write special methods for nodes referencing files. The code would have gotten more cluttered with if/else logic. Since we abstracted early, though, we had a place to put this logic. We want to reiterate that we didn’t expect this difference in attribute casing, but that’s exactly the point – you should expect code to become more complex in ways you don’t expect.
Why do some nodes use filename and others use fileName? We can guess that two different people worked on serializing the different nodes, and they didn’t know the capitalization scheme the other had chosen. Perhaps they communicated verbally and decided on “filename” as an attribute, but one used camel casing. Or perhaps they worked on one node after the other, and forgot what capitalization scheme had been used.
Whatever the case may be, the complexity of the social structure of the third party’s development team is manifesting in this difference of attribute names, and showing through to our codebase. It’s our job to be prepared for that kind of complexity, to be ready to handle it with appropriate structures.

Related articles

Holiday Greetings from Rescale

Located in the heart of fast-paced technology and budding entrepreneurial endeavors, Rescale too saw big changes and movement throughout 2013. Over the course of the year, Rescale expanded its staff to welcome the addition of the following, Software Engineer: Daniel […]

read more »

クラウドHPCの3つの隠れたメリット

Rescaleの米国中部地域担当セールスのJJ Jonesがポストしたブログ記事の翻訳です。 元記事は3 Hidden Benefits of Cloud HPCをご覧ください。 企業は、クラウド上のハイパフォーマンスコンピューティング(HPC)に移行し、柔軟で多様な、従量課金型のHPCリソースへのアクセスを実現しつつあります。同時に、これらの資産を管理することや膨大な資本と時間を費やすことの必要がなくなりました。シミュレーションをクラウドのHPCリソースへ移行することで、企業は以前からは想像もできない規模で多彩な標準タイプおよび特殊タイプのハードウェアを活用できます。クラウドを活用することで、エンジニアリングスタッフの潜在力を発揮し、イノベーションを次のレベルに引き上げることができます。クラウドHPCが成熟するにつれて、これらの明らかな利点は、HPCワークロードに対するクラウドへの動きを促進することです。しかし、定量化を明らかにしづらいその他の利点がしばしば見落とされていますが、それらは本当のクラウドの変革の力を表しています。 この記事では、クラウドに移行することで見過ごされがちな、変革上の3つの利点を見ていきます。 1.トップレベルのエンジニアのリクルーティングや雇用維持 多くの企業が、クラウド資源へアクセスできる環境を保有することが、高い能力を持つエンジニアのリクルーティングや雇用維持に役立つことに気付いています。業界(産業)に関係なく、技術者は仕事に最適なツールを求めています。最速のクルマを持つレースチームが確実に最も有能なドライバーを引き付けるように、最高のツールを提供する組織は最高の才能を引き付けることができます。チームに対して、クラウドで利用可能なシミュレーション機能にに対して深く幅広いアクセスを提供する企業は、現在のハードウェア環境に対する不満を防ぎます。新しい技術が利用できるようになると、エンジニアは既存の社内リソースが完全に償却されるのを待つことなく、流通する最新のテクノロジーにアクセスすることができます。これは、最高の才能を引きつけ維持する企業の能力に顕著な影響を与えています。最近のクラウドカンファレンスでパネルの中であるグローバルCIOが次のように述べました。「従来のインフラストラクチャを利用しているなら、偉大なプレイヤーは得られません」 2.組織のアジリティ 見過ごされがちなもう一つの利点は、クラウドが組織にもたらす全体的な柔軟性です。革新的なアイデアを提供したり、新説を打ち立てたりするためには、企業や従業員は最新のテクノロジーにアクセスする必要がありますが、設備投資は痛みを伴う(そして危険な)決定になる可能性があります。いったん組織が資金を確保し、長期的な調達プロセスを維持すると、現在の作業負荷を中断することなく移行するために人材配置をどうしたらよいのかという葛藤に直面します。クラウドによって、企業は必要に応じて素早く資産にアクセスすることができます。さらに、既存のワークロードを拡張してシミュレーション時間を短縮し、同時シミュレーションを実行することで、チームが日常的なタスクをより迅速に実行できるようになり、将来的に実行可能なタスクに集中できるようになります。この俊敏性と応答性は、企業が自社製品を次のレベルに引き上げることを可能にします。 上記の利点についての最も良い点は、それらを実現するためにクラウドに完全に移行する必要がないことです。既存の社内HPCリソースを使用している組織でも、引き続きそのリソースを使用可能です。才能のあるエンジニアに可能性を与えたり、新製品をリリースしたりする必要がある場合、クラウドの利用が突発させることも可能です。これを行うには、クラウドプロバイダーがターンキーであり、使用に長期的なコミットメントを必要としないことが重要です。(P.S. Rescaleはそうではありません!) 3.ITではなく、組織のコアコンピタンスにフォーカス 最後に、クラウドによって、組織はそのコアビジネスに集中することができます。あなたは飛行機や自動車を作ることができますし、薬を創ったり人工知能ツールをデザインすることもできますが、あなたはIT企業ではありません。企業は、膨大な時間とコストを費やすことに慣れてきており、正しいコンピューティングリソースがコアビジネスをサポートできるようになります。過去には選択肢がありませんでした。クラウドを完全に活用することで、組織はビジネスを推進する重要な戦略目標に集中して、必要なリソースの保守や管理をそのリソースを主要ビジネスとする人々に任せることができるようになります。ITチームは、従業員中心型のプロセスのサポートに集中するのを止めて、顧客中心型の収益創出活動に集中することができるようになります。 これらの隠されたクラウドへの移行の利点は、ROIの計算には簡単には現れません。これらのつかみどころのない側面の真の価値を数値化することは困難です。つまり、明白な利点は、クラウドへの移行を正当化するのにあまりに多く語られています。しかし、これらのつかみどこころのない利点は、組織がクラウドに移行すると必然的に現れます。これらは損益計算書に数値として直接表示されないかもしれませんが、市場シェアや収益性のような中核的なビジネス目標の長期的な上昇を目にすることによって、これらの存在を知ることになります。

read more »