25. Principles to follow
● OOP
● SOLID principles
● Namespaces
● Unit Testing
● Domain Driven Design
26. Unit testing
“A unit test is used to verify a single minimal unit of source code. The purpose of unit testing is
to isolate the smallest testable parts of an API and verify that they function properly in isolation.
“
API Design for C++, p295; Martin Reddy
27. Unit testing - Advantages
● Finds problems early
● Forces the programmer to think
● Safety net for refactoring
● Documentation of the system
● Less bugs
28. Unit testing - Common Myths
● It slows down the development process
● It’s a waste of time
● It is waste when once the project is finished
● But it’s such simple code, why write a test?
Orion evolved from a simple prototype or let’s say “proof of concept” to feature a rich network information system with complex workflows used by hundreds of VHA staff.
I have spent the past 3 months working on Orion and I can see the gradual improvements of its architecture over time. I would like to continue with this trend. I don’t want to engage you in pointless academic exercises. Today, I wanted to give you a set of useful best practices that address the specific issues that we are experiencing on an everyday basis.
One of the major problems I identified is with code maintainability. Let’s have a closer look on how to improve this.
You might have experienced a situation in which a user reported a bug in xls import or a nightly import did not work as expected. Developers would know that many of our import classes are very long and might be comprised of more than 1000 lines of source code.
Like this: [scroll down through BulkUpdateService or DcnLogicalConfiguration class]
Can you work out what is the import class is actually doing? Or how quickly it would take you to understand the business logic written in all of those long lines of code?
Finding a solution in such a situation is not easy due to many reasons:
going through the class and trying to work out why or where the bug happened can be a very strainful and stressful exercise
code has to be retested through the front end and usually the developer’s database is not up to date and so the bug cannot be reproduced on dev’s machine
production system does not have the debugging tools and generally access is restricted
What if I told you that there is a better way? Would you like to see it? :)
Let us continue to follow the import script example. Imagine replacing the very long class [Show the original DcnLogicalConfiguration class]
… with something much shorter and easier to understand [SyncExecutor]
Now you are asking yourselves how can the short class replace the previous long one? Where is all the source code gone?
Well, let me first say that the original class is doing exactly the same only that the business logic is broken into smaller parts which are:
easier to understand (avoiding cognitive overload from reading a long long long class)
easier to maintain (clear interface, single purpose)
increased reusability (avoiding copy&paste, less code = less bugs)
easier to test (through unit tests)
In order to achieve this, I followed these principles and paradigms
Basic principle of OOP
SOLID principles
Unit Testing
Domain Driven Design
Although the original class uses class notation, in reality it is just one spaghetti code broken down into several public methods. It could be as well written as a bunch of functions. In other words, we are not using the features of OOP to its full capacity.
Classes are structures that contain data and functionality. They are put together to represent particular concepts. In order to write proper OOP code, you must follow the following OOP paradigms:
Encapsulation (protect data within class and define clear class API instead of passing arrays around - see EntityId class)
Polymorphism (change behavior by subtyping)
Inheritance
Composition (combine existing classes into new greater parts to model new behaviour)
Delegation (create reusable blocks and create high level concepts from low level classes)
I will not go deep into these topics as they widely known.
In object-oriented programming, the single responsibility principle states that every class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.
Robert C. Martin defines a responsibility as a reason to change, and concludes that a class or module should have one, and only one, reason to change.
[Show the new classes and explain their responsibility - LogicalConfigurationLocator, SyncStrategyFactory, Repository to fetch objects from persistent store]
Also shorter class can better express developer’s intention, such code is easier to understand, maintain and test. Long classes are usually a sign of a code smell.
“A class should be open for extension, but closed for modification.”
In other words, (in an ideal world...) you should never need to change existing code or classes: All new functionality can be added by adding new subclasses or methods, or by reusing existing code through delegation. This is a highly discussed and disputed principle although there is also the following interpretation:
“An interface should be open for extension, but closed for modification.”
[Show Sync Strategy classes implementing one interface and factory class that provides them to a client class. Each of the classes adds a new functionality without changing the client class - NoLogConfFound, OneLogConfFound, ManyLogConfsFound, I could easily implement new strategy by providing new class]
Derived class objects must be substitutable for the base class objects. That means objects of the derived class must behave in a manner consistent with the promises made in the base class' contract. Also complemented by “Design by contract” which can be interpreted as “one should program to interfaces rather than particular classes”.
[Show set of Interfaces allowing to interchange components in injected into SyncExecutor as done in SyncExecutorBuilder class]
The interface-segregation principle states that no client should be forced to depend on methods it does not use. ISP splits interfaces which are very large into smaller and more specific ones.
[Same interfaces as before - they are all very short]
[Introduce TimeMachineObjectTrait & Interface]
In object-oriented programming, the dependency inversion principle refers to a specific form of decoupling software modules. The principle states:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
[Show coupled code where client instantiates another class - it is dependent on low-level modules - DcnLogicalConfiguration line 89]
[Compare with client which has all dependencies injected - possibly defined as interfaces - so it is decoupled from low-level modules - eg SyncExecutor ]
To make the dependency injection easy to use I integrated the DIC from the Symfony Framework that allows to define the dependencies through XML and then just by using the method get (‘some_name’) programmer can obtain particular class.
[show services.xml]
The container is instantiated in the Request class which passes it onto controllers (Handlers) in every request. The method is static in order to make it usable in legacy code that is not using the controller classes.
[show usage in controllers - Site controller - line 50]
[show usage in legacy code - CronManager - line 225]
Until namespaces came around there was just one global namespace and underscore was hijacked for logical structuring of the code
Namespaces is a feature that came quite late to PHP - in PHP 5.3
Namespaces:
Help preventing name conflicts between libraries
Keep the class names short
Allow for better logical structuring of the code. Think as folders for files.
Namespaces are declared using the namespace keyword. A file containing a namespace must declare the namespace at the top of the file before any other code - with one exception: the declare keyword.
In case of name clash we can create class name alias
There three types of class name
Unqualified name - $user = new User();
PHP resolves to current namespace
Qualified name $user - new Some\Package\User();
PHP resolves to current namespace with additional namespaces
Fully qualified name - new \Some\Package\User();
PHP resolves to the particular namespace
We added namespaces to autoloading function. Now PHP opens only files that are being used in any particular request as opposed to opening all files at start.
Improved autoloading supports namespaces which is important factor for better code quality - mainly by structuring code into modules and hence creating bounded context (DDD), keeping the classes short and highly focuses (SRP) and easy testable (Unit testing)
What is unit testing?
Advantages
Finds problems early (in the development cycle)
The process of writing a thorough set of tests forces the author to think through inputs, outputs, and error conditions, and thus more crisply define the unit's desired behavior.
Unit testing allows the programmer to refactor code or upgrade system libraries at a later date, and make sure the module still works correctly - safety net
Unit testing provides a sort of living documentation of the system
When done correctly, unit tested code has less amount bugs compared to not-tested code
It slows down the development process
It may initially take longer to write code, but in short/medium term having unit tests starts paying off, mainly when code change is required
It’s a waste of time
The test code is written once but run many many times to verify correctness of the code. Who remembers the main use cases and mainly the edge cases after few months?
It is waste when once the project is finished
Unit tests turn into asset as they help to document the code and in case the code is reused for another project the tests are still valid
But it’s such simple code, why write a test?
It seems simple, until something goes wrong. Simple code requires a simple test, so there are no excuses.
Unit testing will not catch every error in the program, since it cannot evaluate every execution path in the program. Therefore, it will not catch integration errors or broader system-level errors (such as functions performed across multiple units, or non-functional test areas such as performance).
We still need integration testing, database testing and regression testing etc
Tested and testing class. Assert is the where create the statement: Full name equals “Luke Skywalker”
The usual way - we can see standard output: number of successful tests and failures
By using correct test method names it can provide a documentation of the system - feature by feature. Very useful for testing of complex algorithm and use cases with multiple scenarios
In the root folder is usually placed the phpunit.xml which contains very simple configuration for PHP Unit framework.
Testing classes should follow the names & structure of the tested classes - eg. in a parallel folder structure for easier navigation. Filenames contain suffix “Test”. Testing methods to executed by the phpunit must be prefixed with “test” otherwise they are omitted from test run.
Wiki defines assertion as “a statement that a predicate (Boolean-valued function, a true–false expression)” and “is expected to always be true at that point in the code.”
There is large number of assertions - use the one that best expresses the test.
Mock objects are used to define expectations i.e: In this scenario I expect method send() to be called with such and such parameters. Mocks record and verify such expectations.
Stubs, on the other hand have a different purpose: they do not record or verify expectations, but rather allow us to “replace” the behavior, state of the “fake”object in order to utilize a test scenario