Showing posts with label agile. Show all posts
Showing posts with label agile. Show all posts

Saturday, April 2, 2016

Reinvigorating a large Android code-base



The problem

We faced a classic problem on a recent project. Stop me if you've heard this one before. We are supporting a well established product with millions of users, running on a large underlying code base, but the code base has been building technical debt for years, slowly turning into a Frankenstein, causing support problems. The types of problems we were facing include:

  • Separation of concerns violations making unit test maintenance and creation more challenging, with lower than ideal coverage
  • Threading complexity in places leading to defects that are difficult to find and repair
  • Numerous special-case customizations distributed throughout the code, each addressing specific customer needs that only activate for that customer
  • Reduction in team velocity due to code complexity and the other issues listed above

Challenges of Addressing Technical Debt

In one of my first posts I touched on the challenges of balancing time spent paying back technical debt vs time spent adding new features. It is always difficult to find opportunities to address these types of concerns in a code base because the investment of a large refactoring effort is very high and the direct customer value can be perceived as low (especially outside of engineering) and difficult to quantify when compared to ongoing targeted feature enhancements and defect fixes.

I find the best approach is usually to address refactoring in an iterative approach rather than tackling an entire code base all at once. In this fashion you can balance new feature development with the need to keep your foundation stable.

Solution Overview

On this recent project, however, we were given a task to refresh the entire UI. This was an unusual opportunity to re-evaluate the code base holistically and introduce new concepts and techniques.
We targeted three primary technologies to aid in this effort:

  • Dependency injection using Dagger2
  • Decomposition of Android activities into Model/View/Controllers
  • Usage of RxJava and retrolambda for component communication and thread management

Over the next several posts I will delve into the approach we took, how each contributed to solving the problems identified above, and some of the challenges we faced. 

Saturday, December 12, 2015

Advocating for Robust Automated Test



The conundrum

In virtually all software engineering organizations I have worked, management is aware of the benefits and importance of a comprehensive automated software test suite and the pitfalls of scrimping on test. Yet, it's actual application varies widely across projects and organizations. Often I find that automated test is adopted to some extent, but with insufficient rigor, thought, and attention. Occasionally I have seen teams that don't do automated test at all. In almost every team I have worked, when there is a crunch to get something done, testing rigor is relaxed, dropped outright, or at least postponed until after the crunch is over.

Clearly there are pain points in the process which is leading to this situation.


Pain Points

  • External schedule constraints
    • Deadlines, deadlines, deadlines
    • Customer demo tomorrow
    • Emergency of the day

  • Schedule constraints are often be the biggest reason for reduced rigor in testing methodology. It is especially difficult when they are immutable deadlines coming from external sources such as: 

      • Software delivery to a hardware devices or systems whose release schedules are wholly dictated by hardware availability
        • For example, when working on pre-installed app for a phone, the apps usually either make the delivery date or are pulled
      • Trade shows
  • Challenge of quantifying the cost-benefit trade-off
    • How do we know that we aren't spending more than we are getting in return?
  • Test maintenance burden
  • Difficulty of retrofitting tests into a legacy code base 
  • Up-front cost
    • Test Framework identification
    • Developer training
    • Developer mindset shift
    • CI integration
    • Difficulty in getting "valuable" test metrics

Addressing the Pain

  • External schedule constraints
One strategy to deal with a situation where there is insufficient remaining time to both deliver the software and a full set of developer automated tests is to:
  1. Identify all the required tests and add them to the backlog
  2. Deliver as many of the most critical tests as possible
  3. Supplement the reduced set of automated tests with a more extensive QA test (both manual and automated) for the immediate deliverable
  4. Schedule the remaining unfinished tests for delivery in the following sprint, as soon as the “fire drill” is over
In some especially reactive environments it can be challenging to break out of this “fire drill” mode, as they cascade upon each other. This could be a sign of issues that need to be addressed at the management level,including:
  1. Unwillingness to turn away new business
  2. Unrealistic expectations
  3. Lack of understanding of the impact of these decisions
  4. Insufficient development resources
It is a responsibility of the senior members of a developer team to point this out to management and help work out a plan to remediate the issue. Depending on team charter and business conditions this could be a very difficult problem to solve and isn’t necessarily a management failure. Developers should take an active role in sharing the responsibility associated with improving the process and steering the process back to sanity.

Even in the most aggressive environments, there will always be “some” down-time, which could be dedicated to catching up on test. This is a good time to advocate for sprints focusing on automated test get-well.
  • Quantifying the cost-benefit trade-off
     Perhaps the best way to get upper-management support for test is to demonstrate that the cost in delivery schedule and engineering resource consumption is outweighed by the benefit. This can be challenging to quantify, to say the least. Metrics such as:
  1. Severity and frequency of bug reports
  2. Savings in support development time
  3. Savings in refactoring time
  4. Increased revenue due to release of a higher-quality product
  5. Less need for refactoring 
are difficult enough to measure when you have the data. But, of course you can’t have internal data until you have a well-tested code-base to compare against, so it’s a bit of a chicken and the egg. One approach would be to present these benefits qualitatively, not quantitatively and reference cost-benefit tradeoffs published by other companies who have made this investment.

There is a severe penalty for “catching a code-base up” so the benefit is greater when applied at the beginning of a project.

  • Test maintenance burden
     There is clearly overhead associated with maintaining tests. The amount of this overhead can vary dramatically on factors such as:
  1. Proper test scoping
  2. Test repeatability
  3. Test complexity and supportability
When the tests are properly scoped, repeatable, and straightforward maintenance cost is not so great. Systemic violation of one or more of these factors can easily push the maintenance cost so high that the cost exceeds the benefit. Sometimes a developer or manager will have had past experience working in environments that have embraced automated test, but improperly applied some of these constraints. This can lead to a belief that automated test “is not worth it”. It can be very difficult to challenge a belief system when it is based on experience! It could help, when encountering someone who doesn’t believe in automated developer test to push into the experiences they had, understand where it may have failed, and explain how it might have worked better if applied differently.
  • Difficulty of retrofitting into a legacy code base 
The value of adding good test coverage to a legacy code base is less and the cost greater than if it were applied from the beginning of the project. However, in such environments it may still be valuable when applied judiciously and iteratively in small chunks. For example, before refactoring a bit of buggy, complex, and/or obdurate code, it is helpful to provide strong test coverage for the methods in question and use this to validate the refactored code. Similarly, any time that new functionality is added, good test coverage for that new functionality can be easily justified. Finally, there is always “some” down time in a project, which can be used to bolster tests. I like to target areas of the code that are:
  1. Most problematic (highest bug reports)
  2. Functionally critical
  3. Core (used by many components)
  4. Most complex
  5. Most likely to change
  • Up-front cost
While there are initial costs associated with identifying and implementing a test framework, integrating with CI, and training developers, these costs are manageable and largely scale across multiple projects.

Next up


In upcoming posts I will discuss an automated test strategy that I advocated and adopted for a recent project.

Saturday, July 18, 2015

Design and Architecture in an Agile Environment




I have worked in several "scrum-like" agile environments over the years. I say scrum-like because each adopted some of the principles, but was unable to embrace all. In almost every case the biggest casualty tended to be an inability to totally isolate the development team from shifting priorities in the middle of a scrum. This is a difficult problem to address, particularly when working with exceptionally demanding customers, and perhaps a topic for another day. Another possible discussion item is whether or not "partial-scrum" is scrum at all.

One of the industry trends with agile environments is to tradeoff up-front design and architecture for an "emergent design" iterative environment where coding starts immediately. However, I would argue that agile does not, and should not, imply that there is no up-front design. Iterative development does not mean that you should rewrite the entire system in every iteration. Nor could anyone reasonably argue that this is an efficient use of the team's time. Yet, if you jump into implementation without any design the chances of "coding into a corner" greatly increases, potentially producing exactly this type of situation.

So we shouldn't give up design and up-front thought any more than we should give up good coding standards. Fast development does not have to equal reckless development. A technique I have used in some organizations is to have one or two senior developers/architects sign up for design and architectural tasks as deliverables for the sprint. These deliverables can be tracked much like any other deliverable, and are as integral to the sprint's success as code and QA deliverables. The end-result should be a light design in some electronic format, with buyin from the team and stakeholders.

The only real disconnect with the scrum process of adopting this technique is that typically the one-or-two people working on this task have a different focus from the rest of the team. This work is synergistic, however, as it results in a better definition of development backlog tasks for subsequent sprints. From this perspective, the team performing these tasks could be their own scrum, but often this is not practical because of how small the team is (could be one member) and because the members will often also take other tasks from the ongoing development sprint.

Another approach that I have seen work is to break up chains of sprints with planned one-or-two week intervals for the team to do design and architecture of  backlog items and cleanup work from prior sprints as needed. Whether this work is considered a sprint or not is up to the team, but I have found that occasional "unstructured" time is also useful for a team.

"Does design and architecture play a role in scrum? If so, what techniques have you seen work and which have you seen not work?"