Now we study the design principles.
Before we proceed to study a pattern, we must understand some certain key elements for us to quantify a pattern.
These are the keys elements:
For stating the consequences, we has a list of known aspects for one to consider:
The idea is that one should expose as little information details as necessary to preserve internal flexibility. This gives 2 benefits:
To ensure all functions are working properly, it is always the best practices to perform unit testing against all the public interfaces in a test-driven manner. There are a few stages of testing and validations:
Usually involves labor testing where automation is too costly to implement. Example tests would be:
Implements repeatable testing where automation is feasible and cheap to implement. Usually, these testing runs as frequent as a new patch is given and nightly/daily build using a continuous integration approach like Travis CI or GitLab CI. Example tests would be:
This is usual testing approach for developers:
Usually unit testing is by expectation assertion approach. Example:
Test codes can be organized based on the programming language facilitation. There are 2 general approaches:
Example, in Go, #2 is preferred. In Java, #1 is preferred when using JUnit.
This is to test whether a test suite covers all/most of the source codes. It provides a map (usually heat map) over the source codes. Test coverage studies the test results by depth and the test suite robustness.
This is source codes scanning for possible errors caused by programming linguistic defects. Usually, in a programming language, linters facilitate such scanning. Example: for Go, it is golang-lint.
There are different types of test scopes. Among them are:
This is to keep public facing interface as small, concise, and business needs-to-have basis. Once a public interface is exposed, it is hard to alter since many users are already interfacing with this IO.
Interfaces themselves follows the software lifetime on its own where you schedules a deprecation period for users migration to the new API, then scrap it. This is unlike the hidden implementations under the public interface where developers can refactor them without worrying about user adoption.
Therefore, it is always the best practice to keep public facing interfaces well planned, small and lives as long as possible.
Keep Coupling Low. The higher the order of hierarchy, the less it needs to know everything (comply to delegation). The most ideal coupling interaction is 1.
High coupling is undesirable. The higher the coupling between classes, the worst the effects:
Here is an example of low coupling.
Cohesion simply means small and modular implementation. In ideal condition, we strife for high cohesion. However, the higher the cohesion, the higher the coupling. Hence, one must find the balance between low coupling and high cohesion.
a class with cohesion has relatively few methods of highly related functionalities and does not do too much of work.
Robustness is similar to system resiliency and capability to operate under compromised conditions such as errors or runtime hardware faultiness. This includes defensive checking, error handling, and system recoverability.
Ideally, a system should be highly robust where it knows how to handle every dips and errors it obtained without the attention from the stakeholders.
A robust system is able to strictly control what to send out including error and failure handling. However, user can provide any sort of input. Be liberal to accept all kinds inputs and validate them vigorously internally.
Example, a printer GUI program crashes should not corrupt the entire operating system. Instead, it is handled locally, logged, informed user, and probably restarted itself automatically.
To scale all the modules, the interface of the module and its functions should be flexible enough to replace its own functional abstract for different degrees of testing (as a client / as an internal testing). Such facility reduces duplication and complexity when it comes to the software testing domain.
Modules should be independently testable instead of relying on its dependencies for testing. This way, the module achieves its robustness in terms of portability and reduces integration complexity when an issue (bug/crashes) arises.
This design principle focuses on data recoverability and restoration speed. A highly reliable design can achieve the following attributes.
Design is able to backup itself without compromising data integrity and have trust-able and reliable ways to verify data correctness.
System has a quick self-restoration mechanism to bring back a failed system into operational mode.
That's all about software design principles.