{"id":12078,"date":"2014-03-17T09:51:07","date_gmt":"2014-03-17T07:51:07","guid":{"rendered":"http:\/\/mozaicworks.com\/?p=6448"},"modified":"2014-03-17T09:51:07","modified_gmt":"2014-03-17T07:51:07","slug":"building-changeability-in-design","status":"publish","type":"post","link":"https:\/\/mozaicworks.com\/blog\/building-changeability-in-design","title":{"rendered":"Building Changeability in Design"},"content":{"rendered":"
Development teams everywhere are under increased pressure to deploy more often. In the age of Internet, we can instantly delight thousands of users through a small improvement. It\u2019s rare nowadays even for business customers to be happy with release cycles of years; one year for a new release sounds too slow. A few months is the norm.<\/p>\n
Reducing the length of release cycle is impossible without designing the software with change in mind. Yet we have learned in the hard way that designing too much flexibility for the future brings more trouble than benefits in the medium and long run. That\u2019s how the YAGNI (\u201cYou Aren\u2019t Gonna Need It\u201d) principle was born.<\/p>\n
Developers everywhere are challenged to find the balance of designing just enough flexibility into the system. What can help us meet this challenge?<\/p>\n
To answer this question, we need to go back to basics. What is software design? Who needs it? Why?<\/p>\n
Not that many years ago, many programs were written in a language similar with this:<\/p>\n
section\t.text\r\n global _start ;must be declared for linker (ld)\r\n_start:\t ;tells linker entry point\r\n mov\tedx,len ;message length\r\n mov\tecx,msg ;message to write\r\n mov\tebx,1 ;file descriptor (stdout)\r\n mov\teax,4 ;system call number (sys_write)\r\n int\t0x80 ;call kernel\r\n\r\n mov\teax,1 ;system call number (sys_exit)\r\n int\t0x80 ;call kernel\r\n\r\nsection\t.data\r\nmsg db 'Hello, world!', 0xa ;our dear string\r\nlen equ $ - msg ;length of our dear string\r\n\r\n(source: <a title=\"Assembly Programming Tutorial\" href=\"http:\/\/www.tutorialspoint.com\/assembly_programming\/index.htm\" target=\"_blank\">http:\/\/www.tutorialspoint.com\/assembly_programming\/index.htm<\/a>)<\/pre>\nWith just a few simple keywords and an intimate knowledge of the CPU internals, programmers were able to write code in the assembler language. This is probably the fastest implementation of \u201cHello world\u201d, so why aren\u2019t we using assembly today?<\/p>\n
It had a few problems. First, the code is difficult to read. Writing large applications would be very difficult in this language. Second, the code needs to be rewritten or changed for each CPU it works on. In other words,we needed another language for programmers, not for computers<\/strong>.<\/p>\n
I have come to the conclusion that this is the essence of software design.<\/p>\n
Software design<\/strong>\u00a0means structuring the code so that:<\/p>\n
\n
- It does what is expected<\/li>\n
- It optimizes for desired business characteristics, usually fast change<\/li>\n<\/ul>\n<\/blockquote>\n
There are two parts of this definition that deserve more explanation: how we structure the code and how we optimize for fast change.<\/p>\n
Abstractions<\/h2>\n
The \u201cHello world\u201d program in C looks quite different from the assembler version:<\/p>\n
#include <stdio.h>\r\n\r\nint main(void)\r\n{\r\n printf(\"Hello World\\n\");\r\n return 0;\r\n}<\/pre>\nWhat\u2019s different? There\u2019s a library of functions called \u201cstdio.h\u201d. There\u2019s a main function. There\u2019s a\u00a0printf<\/em>function used from that library. The main function returns an\u00a0int<\/em>\u00a0value.<\/p>\n
When we talk about design, things like: types, functions or libraries are called\u00a0abstractions<\/strong>. We use abstractions to structure the code in easy to understand bits. Programmers read the code above easier than the assembler code. Computers couldn\u2019t care less; give them any correct machine code and they\u2019ll do the job.<\/p>\n
Abstractions have evolved in time. Let\u2019s see the equivalent program in Java:<\/p>\n
class HelloWorldApp {\r\n public static void main(String[] args) {\r\n System.out.println(\"Hello World!\");\r\n }\r\n}<\/pre>\nMost of the abstractions are the same as in the C version. The newcomer is the class.\u00a0A class<\/strong>\u00a0is a different type of abstraction, one that combines data (value members) and behavior (member functions)\u00a0 in order to contain complexity. Again, computers don\u2019t care; containing complexity is only important for programmers.<\/p>\n
Different design paradigms exist, and they are defined by the abstractions used. These examples come from the structured paradigm, that later evolved into the object-oriented paradigm. The functional paradigm has chosen a different type of abstraction: the function. It later evolved into lambda calculus and higher-level abstractions like monads.<\/p>\n
Optimize for Fast Change<\/h2>\n
The C version of the \u201cHello world\u201d program has a very interesting characteristic.\u00a0If we change the implementation of printf in the stdio.h file, the program will do something completely different without changing one line of code!\u00a0<\/strong>This basic observation shows how you can structure your design so that it\u2019s easy to change:\u00a0clearly separate responsibilities\u00a0<\/strong>in different abstractions.<\/p>\n
The good news is that we can do even better. Take a look at this piece of code:<\/p>\n
class HelloWorldApp {\r\n public static void main(String[] args) {\r\n new HelloWorldPrinterToStream(System.out).printHelloWorld();\r\n }\r\n}\r\n\r\ninterface IHelloWorldPrinter{\r\n void printHelloWorld();\r\n}\r\n\r\nclass HelloWorldPrinterToStream implements IHelloWorldPrinter{\r\n public HelloWorldPrinter(PrintStream stream){\r\n this.stream = stream\r\n }\r\n\r\n public void printHelloWorld(){\r\n stream.println(\"Hello world!\");\r\n }\r\n}<\/pre>\n(I realize this design is an overkill for this very simple example. Please bear with me, because it does have an important point.)<\/p>\n
The difference is small but important. To replace the implementation that writes at console with an implementation that writes to a web service, log or messaging system, you need to do two steps:<\/p>\n
\n
- Write a new class that implements the\u00a0IHelloWorldPrinter<\/em>\u00a0interface<\/li>\n
- Change the reference in main to call the new class<\/li>\n<\/ul>\n
You have probably met this pattern before: it\u2019s called\u00a0Dependency Injection<\/em>.\u00a0Dependency Injection<\/em>\u00a0is characterized by one interesting fact: an implementation (HelloWorldPrinterToStream<\/em>) is injected from the main entry point of the program, configuring the rest of the program. It is very used nowadays, and for an important reason:\u00a0changing code is safer when using it.\u00a0<\/strong>To see why, you will need to imagine that this piece of code is part of something much bigger; let\u2019s say 2000 classes that all use\u00a0println<\/em>. If\u00a0println<\/em>\u00a0is used everywhere as in the first Java version, you are faced with the two most common software problems:<\/p>\n
\n
- Fragility<\/strong>: Any change in the implementation of\u00a0System.out.println()<\/em>\u00a0propagates through all calls and programs that use it. One error and everything will break down.<\/li>\n
- Rigidity<\/strong>: You could implement instead a new method called\u00a0printLnToWebService()<\/em>. For any large program, you will need to manually replace all calls to\u00a0println()<\/em>\u00a0with calls to\u00a0printLnToWebService()<\/em>. Needless to say, making the change and testing for it will take a time directly related to how used\u00a0println()<\/em>is.<\/li>\n<\/ul>\n
Using dependency injection means that you can make the\u00a0change safely because you add code<\/strong>\u00a0(which is safer than changing code)\u00a0and inject the new implementation in exactly one place<\/strong>. As an additional advantage, it is very simple to unit test it because it\u2019s isolated.<\/p>\n
Imagine now that when you run the program you can pass in a command line argument that is the name of the implementation to use to write the message. The simple \u201cHello world\u201d program has become extensible on one axis: message writing. Any new implementation is allowed as long as it follows two\u00a0constraints<\/em>: no exception is thrown and avoids concurrency issues.<\/p>\n
Principles and Patterns<\/h2>\n
Dependency Injection is a commonly used\u00a0design pattern<\/em>. The examples above have shown how software professionals identified in time design patterns. They come from practice, not from theoretical ideas, and keep appearing in programs. Because they kept appearing, a group of people decided to document them. That\u2019s how we got the\u00a0Gang of Four \u201cDesign Patterns\u201d<\/a>\u00a0book and the\u00a0Pattern Languages of Programs (PLoP)<\/a>communities.<\/p>\n
But how did we get to the above design? We tried to optimize for flexibility and identified a few ways to do it: separation of responsibilities, adding new features with minimal changes to the existing code, identifying constraints for extensions. These are concerns that were discussed for a long time in the community and were documented as sets of\u00a0design principles<\/em>. The set of principles that focuses the most on changeability is called\u00a0SOLID principles<\/em>.<\/p>\n