Tuesday, April 3, 2012
Let's assume that you are a Java developer who just started working on a large Java application which comprises of 2000 classes and uses multiple frameworks. How would you go about understanding the code base? In a typical enterprise Java team, most of the senior developers who can help you are likely to be quite busy. Documentation will be sparse. You will need to quickly deliver and prove yourself to the team. How would you resolve such a situation? This article offers some suggestions to Java developers starting on a new project.
1. Don't try to understand the whole application
Let me ask you this - why do you want to understand the code in the first place? Most probably you are asked to fix a bug or to enhance an existing feature of the application. The first thing you should not do is trying to understand the whole application architecture. When starting afresh on a project, this approach can be quite overwhelming.
Even Senior Java developers with more than 10 years of solid coding experience may not understand the working of certain parts of the application despite being on the same project for more than a year (assuming they are not the original developers). They may be happily oblivious to the authentication mechanism or the transaction management aspects of the application, for example.
Then how do they manage? They just understand the areas of the application they are working on very well and make sure that they deliver value to the team. Delivering value today is more important than spending time on understanding something that may or may not help you later.
2. Focus on delivering immediate value
So am I discouraging you from understanding the application architecture? No, not at all. All I am asking you is to deliver value early. Once you start on a project and once you have set up the development environment on your PC, you should not take more than a week or two to deliver something, however small it may be. If you are an experienced programmer and don’t deliver anything after 2 weeks, how would a manager know if you really working or reading sports news?
As weeks go by and after you have delivered a few fixes and enhancements, you would start to slowly understand the architecture. Do not underestimate the time needed to understand each aspect of the application. Give yourself 3 to 4 days to understand the authentication mechanism and maybe 2-3 days to understand the transaction management. It really depends on the application and your prior experience on similar applications, but the key is to take the time to understand something thoroughly. Steal the time in between fixing the defects. Do not ask the manager for that time.
Find out if the application has any well maintained unit test cases. When available, unit test cases are a good way to understand large code bases. Unit tests help to start at the smallest pieces of the code base and to understand both the external interfaces of the units (how a unit should be called and what it should return) and their internal implementation (debugging unit test cases are much simpler than debugging an entire use case).
When you understand something well, write notes or draw the class, sequence and data model diagrams. They will help you and your peer developers.
3. Important skills required to maintain large applications
If you are hired for the job, you must already be having good Java skills. Let me talk about the other skills that will help you to perform well on a new project. Most of the time, your tasks on the project will involve either bug fixing or enhancing the application.
There are two important skills that will help you in maintaining large code bases.
3.1 Being able to quickly find the classes of interest
For any kind of maintenance activity, whether it is a bug fix or enhancement, the first task is to identify the classes called in the use case that you are fixing or enhancing. Once you have identified the classes and/or methods to fix or enhance, half the work is done.
3.2 Being able to analyze the impact of a change
After you make the necessary changes to fix a defect or enhance a feature, the most important thing is to ensure that your change does not break any other part of the code. You need to use your Java language skills in tandem with your knowledge of other frameworks to figure out what could be impacted by your change. Below are two simple examples to elaborate the last statement:
- a) When equals() method of class A is changed, calls to contains() method on the lists that contain instances of A will be affected. Unless someone knows Java well, they might not be able to guess this.
- b) In a web application, let us assume that the 'user id' is stored in session. A newbie programmer might append something to the 'user id' as part of a bug fix without knowing that it will impact other use cases that depend on the 'user id'.
So, it is imperative that you know both the Java language and the frameworks used in the application well enough to analyze the impact of a change.
Once you develop the above two skills, most of the maintenance tasks will become easier even when you do not know much about the application. If you are fixing a bug, you will first find the location of the bug, fix it and make sure that it does not break the rest of the application. If you need to enhance a feature or add a new feature, most of the time, you just need to imitate an existing feature that follows similar design.
For instance, in an online banking application, why would the design for 'View Account Summary' and 'View Transaction History' differ greatly? If you understand the design of 'View Account Summary', you can just imitate it to develop 'View Transaction History'.
The bottom line is you don’t need to understand what all the 2000 classes are doing or how the application plumbing code works to fix a bug or enhance the application. If you have the above skills, you can quickly locate parts of the code that you need to change, change it with your Java and frameworks skills, ensure that your changes don't break other parts of the application and deliver your changes, even with minimal knowledge of the application design.
4. Tools to find what to change and to find the impact of a change
Continuing with our theme of delivering immediate value, you should look for tools that help you to deliver immediate value by learning barely enough about the application.
4.1 Tools to quickly find what to change
Whether you are fixing a bug or enhancing a feature, your first task is to find out the classes and methods called for the use case that you need to fix or enhance.There are basically two approaches to understand how a use case works - static source code analysis and runtime analysis.
Source code analysis tools scan the entire code base and show the relations between the classes. There are many source code analysis techniques and tools in the market. A few examples of these tools are: Architexa, AgileJ, UModel, Poseidon, etc.
All these tools use static time code analysis and suffer from the fact that it is impossible to exactly determine the classes and methods called at run time in a use case. The reasons for this are late binding in Java, callback patterns, etc. For instance, the static analysis tools can never infer which Servlet will be called when the Submit button is clicked on a page.
Runtime analysis tools can exactly determine the classes and methods called in a use case at runtime. Some examples of these tools are: MaintainJ, Diver, jSonde, Java Call Tracer,etc. These tools typically capture the call trace at runtime and use that information to generate sequence and class diagrams for a single use case.
The sequence diagrams show all the methods called at runtime for that use case. So, if you are fixing a bug, the bug will most probably be in one of those methods called.
If you are enhancing an existing feature, understand the call flow of that feature using the sequence diagram and then enhance it. The enhancement may be like adding a new validation, changing the DAO, etc.
If you are adding a new feature, find some other feature similar to what you need to develop, understand the call flow of that feature using the runtime sequence diagrams and then imitate it to develop the new feature.
Choose the runtime analysis tools carefully, though. Verboseness is the primary problem with these tools. Choose the tool that offers ways to easily filter out unwanted details and allow you to read and understand the diagrams easily.
4.2 Tools to find the impact of a change
If unit test cases are available, they should be run first to find out if the code changes you made break any other test cases. You may not often find well maintained unit tests that cover most parts of the code for large enterprise applications. Below are some tools and techniques that can be used in such situations.
Again, the two techniques that can be used are static time source code analysis and runtime analysis. There are many static analysis tools available in the market. Some examples are: Lattix, Structure101, Coverity, nWire and IntelliJ's DSM.
Given a class that is changed, all the above tools identify a set of classes that depend on it using static time code analysis. With this information, programmers have to 'guess' the impacted use cases because these tools cannot determine the classes actually called in a use case at runtime.
There are not many tools available in the market that can perform runtime Impact Analysis, except for MaintainJ. MaintainJ first captures all the classes and method called in a use case. Once this information is captured for all use cases, one can easily find the use cases impacted by changes to a set of classes. The precondition for MaintainJ's solution to work is that all the use cases of the application should be run first to capture the runtime dependencies.
All said, currently, you have limited help from tools to quickly and accurately analyze the impact of a change. First recognize the need to conduct proper impact analysis and then depend on your judgement or of other senior members of the team to determine the impact of a change. You might use the above mentioned tools to cross-check your judgement.
5. Two caveats to my argument above
5.1 Do not compromise on code quality
Just because you are delivering quickly without understanding the entire application architecture, you should not compromise on code quality. Below are a few examples where you may be tempted to compromise on code quality for a quick delivery.
Adding new code is usually less risky than changing the existing code that has many dependencies. For example, there may be a method that is called in five use cases. As part of enhancing one of the use cases, you might have to change the implementation of that method. The easiest thing to do might be to copy that method, rename it and call it in the use case you are enhancing. Do not ever do that. Code duplication is bad and there are no two ways about it. Check if you can build a wrapper to that method or override that method or just change it and retest all use cases. Usually, if you stop, think and really apply yourself, there will be a better way.
Another example is changing a 'private' method to 'public' so that it can be called from another class. It is always bad to expose more than what you must. If you need to do a bit of refactoring to put forth a better design, just get on with it.
Most applications have certain structure and patterns of doing things. While fixing or enhancing the application, make sure that you do not deviate from these patterns. If in doubt whether you are confirming to the conventions, ask a senior developer to review your changes. If you must do something that does not follow the conventions, at least ensure that it is local to a small class (a private method in a small class of 200 lines may not ruin the application’s design).
5.2 Do not stop making an effort to understand the architecture
By following the approach outlined in this article, if you are able to deliver by learning barely enough and survive in the industry, you might stop making an effort to understand the application architecture. Doing so will not help your career in the long run. This can be avoided by working on bigger tasks as your experience increases on the project. There will be larger enhancements like building an entirely new feature or a change that affects the fundamental design of the application. By the time you are attempting such changes, you should understand the application architecture reasonably well. The approach illustrated in this article is designed to get you up to speed and help you deliver in minimal time and not to dissuade you from comprehensively understanding the application.
The whole point of this article is to focus on delivering value quickly by learning just enough about the application. You can do that without compromising the code quality.
If you are fixing a bug, quickly find the location of the bug and fix it. Use the runtime analysis tools to locate the bug if necessary. If you are adding a new feature, find a similar feature, understand its call flow (using the tools if necessary) and enhance.
You might say it all sounds to simple, but is it really practical? Yes, it is. But the pre-condition is that you have good Java language skills and know the frameworks well enough to first make the code change and then to analyze the impact of that change. Better skills are required to analyze the impact of a change than to actually make the change. You might seek the help of a senior developer to analyze the impact.
About 50% of IT operational budgets go toward simple bug fixing and enhancements. By following the approach suggested in this article, it is possible to save a considerable amount of money, spent on such maintenance activities.