CodeBork | Tales from the Codeface

The coding blog of Alastair Smith, a software developer based in Cambridge, UK. Interested in DevOps, Azure, Kubernetes, .NET Core, and VueJS.


Project maintained by Hosted on GitHub Pages — Theme by mattgraham

Wow, it’s been quite a while since I updated my Code Complete series, and I’ve got quite the backlog to wade through now! Looking at the last save date on this post, it’s been sat around for three months waiting to be written, so I’m sorry for being so slack, and I’ll get on with writing it now…

This post covers some key design practices, and is the last post on Chapter 5, Design in Construction. The simplest design practice is to iterate your designs. Your second attempt at solving a problem is nearly always better than the first: this is somewhat because design is an example of a “wicked problem”, i.e., one that can be clearly defined by solving it in whole or part. Note also that iteration requires the critical appraisal of the design after each iteration, allowing for kinks, mistakes, and omissions to be corrected.

Another option available to you in design is the notion of divide and conquer. This is a useful tool for decomposing a complex system into different areas of concern, and will help introduce greater modularity into your system, making it more loosely-coupled. All good stuff. If you run into a dead end when dividing your system, fence it off and start a new iteration using the knowledge that you’ve gained from the first attempt.

Useful with divide and conquer are top-down and bottom-up decomposition. Top-down decomposition starts at a high level of abstraction, drilling down into the detail in successive applications of the technique; you should keep decomposing until the next level is “obvious” and therefore easier to code than decompose further. The advantage of this approach is that it makes complex systems easier to deal with: it introduces fewer details at any given time, and allows you to defer construction details until it is most appropriate to deal with them.

Bottom-up decomposition works in the opposite direction, starting with the specifics and building successive generalisations on top of them. This is useful when the top-level abstraction is poorly-defined or -understood; sometimes it’s just easier to start with the specifics. It also provides a couple of advantages in construction: utility functionality is identified earlier (thereby producing a compact and well-factored design); and code reuse becomes more obvious and easier to consider. However, it is very hard to use exclusively, and sometimes you can’t build a coherent system from the pieces identified. As a result, you should bear in mind the following tips when using bottom-up design:

Sometimes, however, you just have to try something out to know if it will work, and this is where experimental prototyping has a role to play. It can be very easy to slip into making a polished prototype, or at the very least, something more than that which is most definitely must be, which is the absolute minimum required throwaway code. For example, when assessing database performance, create the right number of tables called Table1, Table2, etc., with the right number of columns each called Column1, Column2, … , and fill the rows with junk data. In order to achieve the best results from prototyping, the question forming the basis of the scenario must be specific. “Will X work?” is not specific enough; “Will X support Y under assumption Z” is. Finally, the code created must be throwaway; if there is any belief that it will be re-used, the developer(s) will end up producing an implementation and not a prototype.

Collaborative design practices are very useful tools to keep on your belt; the value of an extra pair of eyes is not to be underestimated. There are many ways of utilising a collaborative approach ranging from the informal (bouncing ideas around with a co-worker, whiteboarding) to the formal (pair programming, formal inspections). If you don’t have a team to collaborate with, it’s possible to get some of the benefits of the collaborative process by using self-reviews: produce a design, put it away for a week, and then come back and appraise it. You will be looking at it afresh, and will be able to evaluate it as another might.

McConnell poses the important question of how much design is sufficient for a project in this chapter, and answers it with the following table:

Factor Level of Detail Needed in Design Before Construction Documentation Formality
Design/construction team has deep experience Low Detail Low Formality
Design/construction team has deep experience but is inexperienced in the application's area Medium Detail Medium Formality
Design/construction team is inexperienced Medium to High Detail Low-Medium Formality
Design/construction team has moderate-to-high turnover Medium Detail -
Application is safety-critical High Detail High Formality
Application is mission-critical Medium Detail Medium-High Formality
Project is small Low Detail Low Formality
Project is large Medium Detail Medium Formality
Software is expected to have a short lifetime (weeks or months) Low Detail Low Formality
Software is expected to have a long lifetime (months or years) Medium Detail Medium Formality

There are a couple of caveats, of course. First, the terms “Medium detail”, “low formality”, etc., are a little vague (particularly the documentation measures), so require guess-work (ideally grounded in experience) to decide what constitutes “high detail”. Additionally, two or more of these factors might be relevant to your project, and sometimes they may contract each other (e.g., a small, long-lived, mission-critical application). In these scenarios, you need to use your judgement to decide how far to go.

Finally, McConnell describes ways of capturing your design work. These range from using formal design documentation methods, such as Class-Responsibility-Collaboration Cards (CRC cards, produced using index cards, listing the class name, its responsibilities and classes that co-operate with it) through to using wikis to capture design discussions and decisions, and inserting design documentation into the code itself (e.g., using file/class comments, which are particularly powerful if coupled with a comment-parsing system such as JavaDoc or Doxygen). Other suggestions include writing email summaries to the team and archiving them publicly; using a digital camera to photograph whiteboard drawings; using UML diagrams at appropriate levels of detail; and saving design flip charts (which can be used instead of re-writing all that information into a formal document).