November 10, 2013
In an earlier post, I listed reasons why automated tests are foundational for reuse. In this post, want to provide some approaches that will ease automated testing of your components.
- Mock API interactions when using external dependencies. Mocking will reduce runtime dependencies and make your unit tests faster and more robust. Use JUnit with Mockito – mockito has excellent support for a variety of mocking use cases.
- If an external dependency is required from multiple classes, you can define an Adapter that will wrap the external API via an interface. The interface can then be mocked or stubbed and will provide an abstraction layer for your classes. Word of caution: abstractions are leaky and resist the need to wrap every single API provided by the external dependency.
- Use in-memory databases and provide a consistent API for your tests. A common class could initialize and clean up the in-memory db and can be leveraged from tests. Alternatively, it can be provided as an abstract class that your tests can extend. Take the opportunity to standardize location, naming, and directory structure of test resources – if you are using maven for instance, the db related data files can be placed under src/test/resources/db/<db-name>. Finally, this is very useful in ensuring that the database-bound code is indeed testable – forcing the in-memory db test will make technical debt apparent.
- Use db-deploy or some automated database deployment tooling to define and populate databases from tests – these can enable developers to define and execute tests without sharing / corrupting each other’s data. It will also make your database deployment repeatable and well tested eliminating a key deployment risk.
- Provide a common API for routinely used tasks for developers – e.g. APIs that can create test data in in-house / proprietary formats, parse, and populate appropriate data structures will be useful.
- Use JUnit Rule extensions for having a common API for developers – provide a custom rule that will manage the lifecycle of a legacy component or a API that is difficult to use – these are all opportunities to both facilitate testing and add value via reuse.
November 3, 2013
Here are some tips when authoring web service clients:
- Decouple connectivity from request construction. This will isolate variations in input construction and the mechanics of service invocation cleanly separated. Additionally, the request construction might depend on the particular resource – e.g. they can be set of query string parameters or a more complex object structure.
- Connectivity logic should encapsulate the service URL and automatic-retry considerations. The client can automatically retry GET requests specified number of times if invocation encounters a connection timeout. It should also ensure response is OK (either via HTTP status codes or by examining appropriate response-specific data structures).
- Don’t swallow exceptions – the service might return a resource not found or an internal server error – the code that is using the client should be given the flexibility to deal with these exceptions appropriately – the client code shouldn’t assume or mask these exceptions. When in doubt, don’t suppress runtime exceptions.
- Decouple domain logic from service client – domain logic might dictate whether or not a service call needs to be made, or the nature of input resource data, etc. – this logic is more likely to change per the consuming application’s evolving requirements and shouldn’t be hosting service invocation code in the same class.
- Provide reusable API hooks for addressing cross-cutting concerns – such as response time capture and input and output messages – if you want to report response time trends when invoking a service you will not want to clutter this all over the consuming application’s codebase – the client can and should centralize these.
Remember the above is useful whether you are consuming a service or providing clients for your prospective service consumers.
November 2, 2013
Reading the book Code Simplicity by Max Kanat-Alexander. When you have to make lots of design improvement and implementation decisions it is important to keep our solutions simple. Reducing complexity is an important aspect of good code and particularly relevant to systematic software reuse – so hoping to learn new concepts from this book.
You might want to also check out Max’s interview on the rewards of simple code.
November 2, 2013
You can wait for that dream initiative or project to build a whole new set of reusable components that will magically make your teams more productive. The only issue is – it is highly likely that it will be just that – a dream. Instead of planning for systematic reuse, start executing on it by taking a few simple steps. Ask yourself the following questions:
1. Are you capitalizing on identifying and sharing common components with your department / team?
2. Is every project encouraged to continuously refactor and harmonize classes for reducing redundancies? If not, why not?
3. Do you have code that caters to common infrastructural concerns – logging, exception management, alerting, monitoring, metrics.? If yes, is their reuse mandated via common framework hooks that your developers are already using? If not, what is preventing adoption of these concerns into your development stack? Ask your developers and listen to their concerns – you will need to unearth and attack the root causes behind reuse barriers.
4. Do you utilize ad-hoc, informal pairing and code review sessions to identify and harmonize similar / duplicate / redundant classes? If you review code the first time before a project go-live, odds are you either will regret missed opportunities or bemoan the lack of time within your development cycle for making improvements. Key is to intervene early and often and front load your investments for systematic reuse
5. How do you ensure reafactoring to reuse opportunities are tracked? do you create improvement tickets and action them on a best-effort basis or are they managed as part of the product backlog of things that have to get done? If its the former, it will be difficult to make much progress. Creating and tracking tickets will will provide visibility – however, for you to make tangible progress in acting on them you need to partner with developers and development managers to action work on an ongoing basis.
These are just example questions to help you get your journey started and it should be abundantly clear that discipline and continuous alignment is key. If you don’t do anything else, just force your team to converge on a common implementation on key functionality. You will be surprised what discipline can deliver.