© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
S. RylanderPatterns of Software Constructionhttps://doi.org/10.1007/978-1-4842-7936-6_4

4. Build

Evolution #2
Stephen Rylander1  
(1)
Glen Ellyn, IL, USA
 
In the build evolution, the team constructs software using the best techniques available to them. LIFT is not prescriptive of how to write code. Here, we will examine what most software looks like and extract patterns to consistently win this evolution. And even though agile is not a panacea, some of the techniques are helpful and the terms are common enough not to redefine them – starting with what a sprint looks like in the LIFT world.

Category

Description

Target

Increments of working software as close to the live environment as possible.

Inputs

Sliced up release plan stitched together with a steel thread approach.

High-level work-items in your WMT.

Outputs

A releasable version of software.

Visibility

Clear view of WIP, velocity, and defects in WMT.

The Win

You have Working Software in a non-dev environment that has been tested successfully.

Anatomy of a Sprint

Few things cause more pointless disruption to teams than the fallacy of the two-week Sprint. Even writing this is dangerous. There are legions of agile practitioners and agile desk jockeys who are arbitrarily tied to a two-week delivery cycle. Well, two doesn’t mean anything. Why not one, or six? Look at earlier books on agile and you’ll see that – as learning to be agile in mindset, practice, and delivery has nothing to do with compressing development and testing into two weeks. Let it go.

Therefore, to make the point, LIFT recommends three weeks or more.

Why three weeks? Three weeks allows organizations that have multiple departments and legacy overhead (again, LIFT is for all teams and companies, not fantasy teams) to coordinate. If your team could really use some extra time for regressions testing, some time to plan the next sprint, and some remediation time, then more than two weeks is needed. I’ve seen consistent sprint processes of four weeks, six weeks, and two months.

The objective is consistent sprint success and the means to this is consistency and discipline. As of this writing, I’ve seen the three-week sprint successfully increase quality delivery on at least five occasions, with different teams, different firms, and different cultures.

Consider this scenario:

A financial product that is considered one application by stakeholders (sales, product management, customers), but it’s really four different underlying applications stitched together, six services your team maintains for your product, two services you maintain and are shared with other internal teams, consuming three services from other teams, and altogether there are five different databases in play. This is all laid out in Table 4-1. We won’t even mention what the infrastructure looks like!
Table 4-1

Dependencies piling up

Dependency

Owner

Quantity

Applications

You

4

Services

You

6

Services (shared)

You

2

Services

Other teams

3

Databases

You

5

 

TOTAL

20

Some quick math and the team is dealing with 20 dependencies. What is the sense in taking a team that must manage this much complexity and ask them to release new features to production every two weeks? Here are five possible negative outcomes from this setup:
  1. 1.

    Miss on delivery date

     
  2. 2.

    Miss on scope

     
  3. 3.

    Miss on quality

     
  4. 4.

    Miss on planning the next sprint (from exhaustion and time compression)

     
  5. 5.

    Burns out the team

     

If you really like two weeks, then go for it. Maybe you have enough release, test, and other automation with verification that the shorter timeline will yield good results. But beware the hamster wheel if you’re just getting started. At the end of the day, no one cares. Find the duration that works. You want success – not metrics.

In Figure 4-1 is the anatomy of a three-week sprint. What follows the chart are the important activities across the weeks.
Figure 4-1

Anatomy of a three-week sprint

How to read the three-week anatomy:
  • S stands for Sprint. So, S+3 is 3 days after the start of the sprint.

  • R stands for Release. Therefore, R-1 means 1 day before Release.

Week 1

The first week is about making active, meaningful headway on the most important development stories loaded up from planning. Table 4-2 below shows planning for Week 1 happens during Week 3, so don’t worry if it doesn’t make sense yet. The overall goal of Week 1 is to develop functional software and attack the hardest problems.
Table 4-2

Week 1 Activities

Activity

Description

Sprint Start (Sprint Start – 0 days)

This is the rocket launch.

Development + Test

Attack the most complex stories first, in the simplest steel thread approach possible.

Always do the tough ones first. There is plenty of research out there on why finishing the difficult things in life before the easy breeds consecutive success.

Test Writing Complete

The great majority of test cases are documented by now. These could be in document form, in the work items, or another tool. This is a team choice. (Prefer to include them in the original work item when possible.)

Maybe you have a test case management tool – hooray!

Week 2

When moving into Week 2, expect to start really closing stories, getting test signoff on important items, and starting to think about the release. The Release Prep Meeting is more important than it may sound. It’s doubtful it sounds advantageous at all – well think again. This session surfaces the details that can completely derail a release. If there is one place an engineering team gets slammed by its stakeholders it’s botched releases – don’t be that team. Table 4-3 belows outlines the Week 2 activities.
Table 4-3

Week 2 Activities

Activity

Description

Mid-Sprint Review

This is an opportunity to review all the work completed so far – development, test cases, functioning software, automated tests. Look at the plan and see where the sprint is compared to where you are expected to be. Did the Big Rocks get attended to? Is there complexity left for the end? If so, this is the place to identify these and adjust.

Release Prep Meeting

Start planning for what a release looks like. Are there database schema changes... how will this be handled? Does the security team need to review some new components? Any changes required to the deployment script? Will a new feature need to be flagged on/off and who will do this? Use this time to capture the variables that go into a successful release, plan for it, highlight risks, and find solutions.

Code Complete

Complete writing new software and make final commits! (They aren’t final/final because we have other activities like test/fix in Week 3.)

Week 3

The third week is really about Week 1. Yep, now we are planning to start again, fixing must-have defects for the release, and physically releasing the software. Some of the planning activities listed in Table 4-4 can be combined as well. For instance, some teams do not do the Tasking session because the maturity of the team only needs the Planning & Estimating – then when the sprint starts, they naturally order and execute the most difficult work first. Make sure engineers are driving this decision and not project managers, product managers, or scrum masters. Engineers build the software, therefore order the work.
Table 4-4

Week 3 Activities

Activity

Description

Pre-Planning (Sprint Goal and Story Slices)

Planning is a legitimate, organized, controlled activity. This is the session before Planning to pull in outstanding issues, tech design, dependency changes, etc. that should be included in the upcoming planning session. This is not an all-team activity.

Identify the goal for the next sprint. Look at what you want to accomplish and see if you can slice it up into something that makes sense. But don’t worry about it being perfect.

Must-fix Standup

There is a ton of testing going on right now. The must-fix standup includes only items that must be fixed to deliver on the goals of the release. Nothing more. Nothing less.

Retrospective (Retro)

The Retrospective is any format the team chooses to reflect on the sprint, what worked, what didn’t. There are many options out there to choose from to run this session.

Planning & Estimating

Planning is taking the work that is targeted to be built and released, discuss it, estimate it, sequence it, and generally create the overall plan for the next sprint. Final story slices happen here. This is usually an all-team activity.

Release Sign Off

Someone from each function (engineering, test and product) gets together and reviews what is in pre-prod, and only then agree to do a release. To make this nice and tidy, document the decision and stick it online where everyone can find it later.

Release

Deploy the software and make it available for your users! Woo!

Task Writing

Take the Plan (from Planning and Estimating) and start to break it down into smaller tasks for the first week of the sprint. This helps everyone understand their role and maximize contributions as the team starts running at S-0 again. (Task writing can be optional and some teams do not like to go to this level.)

Test Case Writing

Write the major test cases, create placeholders for the big rocks, and activate the part of the cycle that makes sure we handle risk.

Sprint Prep – Gap Day

Breathe. Take a break. Clean up code, whiteboard ideas, and prepare for the next sprint. Don’t underestimate the impact of a good gap day(s) on prevented burnout and increasing cohesiveness of a team.

Most Software...

Most software looks like Figure 4-2.
Figure 4-2

Most software looks like this

Sure, your system doesn’t look exactly like this. But if you’re inclined to read a book about software systems it may not be too far off. You wouldn’t be reading this unless there was something you knew could be done to increase your speed, quality, or efficiency. Add in some more APIs, increase the number of databases, include complexity in stored procedures or some other open-source java libraries, etc., and you can make the mental match to your day-to-day reality. There are innumerable variations out there in the details – but business applications follow patterns. Lots of services, few services, thick clients, thin clients – there is not much new under the sun until quantum computing takes off. And if that happens, then hopefully we don’t need these books anymore.

Your product is not a snowflake. Your team is not a snowflake. Understanding this will give you freedom to focus on what matters – shipping software.
Figure 4-3

The secret pattern of all software

Does it matter if most software (business software) is similar like Figure 4-3 above? Yes. 100% yes! The similarities allow LIFT to work. There are only so many technologies, architectures, and dependencies out there and pulling your view up a few levels will allow you to see the pattern.

Non-Functional Requirements Pay the Bills

As software professionals, we aren’t paid to solve business problems. That’s just nostalgia from mainframe days. Old stuff you’d read in Peopleware or books leading into the turn of the century talk about “solving the problems of the business.” Right. That’s quaint.

We are paid to handle the complexity, connect the components, enable UIs, secure data, integrate systems, and deliver functionality that in turn incrementally puts the product and business in a position to grow. What is built must work across devices and oceans. It must be superquick and everything that uses a mouse or a tap to navigate is compared to Google Maps or an iPhone.

How do we know this profession isn’t all business “problem solvers”? It’s so obvious you’ll miss it if you don’t look hard. Global outsourced software development. We don’t outsource, offshore, nearshore to solve business problems. We do it to produce working software.

Produce Working Software and then trust that the product solves the problems set out by the product, leadership, and marketing teams. Working software to Product and Sales teams really means software that runs all the time, quickly, and as expected or better. Working Software to engineering teams can be inspected, debugged, observed, changed, and operated.

Non-Functional Areas to Completely Own

What does it mean to own these areas? It means professionally making sure that the areas receive attention and are included in the build and construction of software.

The key non-functional topics for the BUILD evolution of LIFT:
  1. 1.

    Defensive programming

     
  2. 2.

    Heavy logging

     
  3. 3.

    Debuggable software

     
  4. 4.

    Performance

     

Defensive Programming

Write code expecting it to fail. Amateurs write software for happy paths. Professionals write software expecting it to fail. Professionals limit hubris and know they aren’t omniscient. Expect your code to fail, software to fail, dependencies to fail, systems to fail, and servers to fail. Don’t have servers? No problem. Your serverless functions can also fail for many reasons – like network or permissions.

So, how do you program defensively? There are two primary ways: exception handling and guard statements.

Exception handling, for simplicity’s sake, in languages like Java and C# are try/catch statements which you can see in Table 4-5.
Table 4-5

Exception Handling

Basic Exception Handling

try{

   callSomeRiskyMethod();

}

catch(Exception e){

   log(e);

   doSomethingElseMaybe();

}

Assuming you are a full-time programmer or have been a programmer at some point in your career, this looks trivial. You may even be wondering how something so simplistic would make it into any modern book on software. Well, building software is about a strong foundation and working its way up the stack. Is your exception handling really handling exceptions?

Take these steps:
  • Have code reviews check for exception handling.

  • Is the handling doing what it needs to do?

  • Who is taking responsibility? It shouldn’t be a manager – look for a respected lead engineer/senior engineer who will put a flag in the ground for quality.

Next up are guard statements, which are on a level field with exception handling. In fact, more so for the operations of a working system. While good exception handling will allow software to avoid “blue screening”1 and crashing, proper guard statements make sure that the execution of code routines is accurate. See an example of this in Table 4-6.
Table 4-6

Guard Statements

Guard Statements

//EXAMPLE 1

//classic check for null reference exception

if(accountBalanceObj == null){

   getFreshAccountObject(currentId);

}

else{

   //continue execution

}

//EXAMPLE 2

//Checking for a value that affects calculations

if(accountBalance > MAX_CORP_BALANCE){

   return(MAX_BAL_EXCEEDED);

}

Software engineers can practically end the world by not checking for null! Imagine how many code errors are caught while engineers are debugging during initial development. Do you have that mental picture? Now consider how much slips through the cracks! Checking for null is the easiest way to protect against both inaccurate data and preventing exceptions from bubbling up and crashing methods, routines, executions, services, and systems overall. LIFT is a system and systems include basics. Check for null.

The second example is focused on accuracy. In this scenario, when an account balance exceeds the predetermined amount, the function returns with that error code. The snippet doesn’t say what happened before or after and for these purposes it doesn’t matter. Protecting the accuracy of the business logic is what matters. In this case, the method returns. In other cases, the code may assign a tracker variable a value and that variable makes it all the way down to the end of the method. There is a time and place for both approaches – and any other of myriad approaches. Don’t judge the examples, mind the principle it exposes.

Guard against conditions that will make the correctness of the program fail. This will prevent defects, yes. It can also help to limit inconsistent errors in a system which are exponentially expensive to find and fix. Building software in LIFT focuses on producing working software.

Hello, customer service...

Have you ever been on the phone with customer support, and the agent has a long pause followed by some keys clicking, a sigh, and “sorry, my computer isn’t working?” Guess what? That software, built by a software engineering team, has a severe bug. And that bug is causing broader issues than anyone can readily see.

Aggressive Logging

Log like your life depends on it. Seriously – take it seriously! Imagine the derisive laughter of the team taking over after your departure when they tell their product managers the fixed runtime issues software you build because they added logging statements and then read them! Use this as motivation not to be remembered as a hack.

The old team didn’t log anything

New Senior Engineer Paul: “Hey, Sanjida (the product manager), guess what. We added logging in the last sprint and see that the shipping routine loops 5000 extra times because of some bug! We fixed it and without even performance testing it can say it’s going to free up cycles for customers.”

Sanjida (PM): “Really? That’s great, the last team just said it was slow because it was old code. Why didn’t the last team add logging?”

Paul: “Hmm. Well, I don’t want to throw anyone under the bus... maybe they were busy?”

Sanjida: “Lazy?”

Paul: “Yeah. Probably.”

Being lazy isn’t professional. It’s an embarrassment. Here are five reasons to log aggressively:
  1. 1.

    It’s easy to log.

     
  2. 2.

    It lets operators and others observe the runtime of a system with little effort.

     
  3. 3.

    Developers can get details out of their code at a scale that’s not possible in local development.

     
  4. 4.

    Having log statements allows DevOps/secops/it-ops to construct other systems to make sense of logs, like alerts and events.

     
  5. 5.

    Logging is the simplest way to pull data out of a system with very little investment.

     

Questioning logging vs. some other grand idea is excellent in theory. But why push against a technique so well understood and adopted? There are plenty of other challenges during BUILD without sweating something like this.

To continue keeping it simple, log these five pieces of information, and your ops team will high-five you:
  1. 1.

    Log every successful login or failure with an encrypted user ID, date stamp, and user type.

     
  2. 2.

    Log messages from exception handling blocks that you didn’t expect to hit.

     
  3. 3.

    Log messages from exception handling blocks that you’ve designated as problematic but expected.

     
  4. 4.

    Use log levels, info, warn, error, fail, etc. Keep them consistent.

     
  5. 5.

    All network connection timeouts get logged.

     

What do you get from this effort? Your operations team can operate the system without calling you and engineering and generating product incident alerts. And as you look down that list, really consider the consequence of #5. Teams can be at odds between “code vs. network” issues. Log the network failures, and there is less to fight about, and you’ll help the network team get to the bottom of the problems faster and solve for everyone. Win-win.

Debuggable Software

Software is incredibly complex. Debugging software is a primary activity of software engineers today and far into the future. Therefore, reducing the debugging effort on the software you and your team write pays itself today in both the short term and the long term. After all, as soon as the code ships it’s legacy. Someone, maybe you, will debug it shortly.

There is much literature out there on writing debuggable software. For LIFT, focus on these three areas:
  1. 1.

    Small functions

     
  2. 2.

    Loose coupling

     
  3. 3.

    Code comments

     
These three areas aren’t going to make the cover of your favorite programming website. They are the meat and potatoes of system engineering and lean towards making software work in small increments while thinking about the end state vs. being a hot trend technology.

A complex system, contrary to what people believe, does not require complicated systems and regulations and intricate policies. The simpler, the better. Complications lead to multiplicative chains of unanticipated effects. Because of opacity, an intervention leads to unforeseen consequences, followed by apologies about the “unforeseen” aspect of the consequences, then to another intervention to correct the secondary effects, leading to an explosive series of branching “unforeseen” responses, each one worse than the preceding one.

—Nassim Nicholas Taleb, Antifragile

Small Functions

Write your functions small, as in short, so you don’t end up with spaghetti code, like in Table 4-7 below. It’s that simple. No fancy refactoring or software patterns are required to do this, so even the junior engineer can contribute on day one. Functions call functions. Methods call methods. Rinse and repeat.
Table 4-7

Small functions

Small Functions

Main() {

   getCurrentId();

   getSystemObject();

   log("complete")

}

private int getCurrentId(){

   //call db

   // return id

   //catch exceptions

/* This is considered a short method.  */

}

private int getSystemObject(){

   obj system = caller.System.Context.Call.Current;

   return system;

/* This is considered a short method.  */

}

Avoid Coupling Components

Loosely coupled software components are the second key in debuggable software. It’s a term that’s thrown around at times by know-it-alls, newbies, or management. Still, it’s a valid design concern and one that can assist with debugging software, so should be respected.

The concept is simple, but hard in execution for new software engineers. Don’t let the internals of Component A know Component B’s internals. You can see this concept illustrated in Figure 4-4. This goes into all kinds of design thinking around abstraction, interfaces, and polymorphic behavior – but don’t worry about that yet. Just stick with A doesn’t know how B works. A only knows how to work with B – but not what B does internally. This is accomplished differently depending on the language in use. In Java, this is usually an interface or abstract class. It’s also achievable via naming conventions and data hiding in just about any language.

What Coupling Looks Like
Figure 4-4

Loose coupling of components

Anti-Corruption Layers

If there is one practice that can easily, and I mean quickly, be picked up by even an average experience development team, it’s the concept of anti-corruption layers published by Eric Evans in his book Domain Driven Design. Domain Driven Design (DDD) approaches software projects by thinking, modeling, and building around the domain using abstractions that work. This is different than the traditional object-oriented programming approach to model the real world with software. As we all now know, an e-commerce shopping cart doesn’t mimic the shopping cart in your local Walmart store well at all. Software is nuanced and complex; a shopping cart made of metal is as simple as it gets.

Would you like the benefits of micro-service and distributed architectures like resilience, independent versioning, and deployment velocity without rewriting your world to micro-services?

Well, you’re in luck, because the anti-corruption layer acts as a facade or intermediary between System A and System B. It translates what comes out of System B (someone else) into your language and data structures in System A (you). This prevents System B from leaking into your system, and that is more amazing than it sounds.

From experience, all systems, data, and APIs that aren’t under your direct control are foreign forces with unclear intentions. That’s right. Managing dependencies is serious business as your product’s health and long-term maintainability is on the line.

Think of it like this: if you integrate the Foo API and your app breaks or is slow, it’s your fault. However, The Foo API team has no problems. They are the service provider, and you are the user. Sure, the service provider has different stressors (reliability, uptime, versioning, etc.), but your failure is not their failure. Your product’s loss is your product’s failure. Period. So, protect yourself because, in software, no one will come to save you.

Is an anti-corruption layer this additional work? Yes, of course, it’s extra work. But it pays for itself very quickly. Generally, in life, if it’s too good to be true, it probably is. Will that new exercise bike make you lose 20lbs? Not right away. Can you buy a stock that will surely deliver 300% gains? Probably not. So, you’ll have to put your inner pessimist on mute for a bit because creating an anti-corruption layer to protect your system from another team’s system will return significant gains. And it will produce those gains almost overnight.

Consider System A (e.g., your system/product/app/service) is responsible for calculating car leases’ historical prices. For this to work, A must call into APIs from System B, which return the last three years’ costs at different fidelity levels for all vehicles manufactured and headquartered in Europe. They produce data structures ABC, which have GUIDs (globally unique identifiers). This is not unusual, nor is it a problem.

Look at Table 4-8 – System A also has to integrate with System C, a daily FTP drop of 5-year prices for all vehicles manufactured and headquartered in Japan. This integration has yet another data structure and because they are sane, use an increasing numeric value as their identifier.
Table 4-8

Systems at play

System B API – Europe

System C FTP Drop – Japan

GUIDs

Numeric Identifiers

JSON

CSV

You are at a crossroads. Do you want your development team leaking System B GUIDs and System C numeric identifiers down into all your app code? Do you want your junior engineer to understand these two data sources’ internals and intricacy and then start coupling external GUIDs to your internal models?

Thus, the creation of the anti-corruption layer.

All the code goes into this layer. This layer maintains the contracts with the other systems, performs data translation, and maps your internal models. The internals of System A now work with the anti-corruption layer, and it can’t break. It’s your model to your model. Not your model to N models. And there aren’t foreign abstractions spreading across your codebase. See Figure 4-5.

If this interests you, please read more about Domain Driven Design in Eric Evans’ book. It’s also a prevalent practice, and a quick Google search will yield more than enough results to get started.

Anti-Corruption Layer Diagram
Figure 4-5

Anti-corruption layer working with services

Performance

This is a two-edged sword.

One edge says not to overoptimize, which can also be used to mean don’t optimize early. New engineers and those who lack humility throw around old Donald Knuth quotes like “Premature optimization is the root of all evil.”

Really? I doubt it.

Let’s dispel the misinterpretations of this quote first. He said optimizing algorithms by nitpicking at code before it was profiled was a waste of time. He wasn’t saying that optimizing software before it fell over under use was a waste of time.

Take this to its logical conclusion and optimizing the right things early is a healthy practice. Consider a system that handles ecommerce orders – what if it couldn’t handle more than three concurrent orders? That’s not helpful. Or a system that exchanges records with a regulatory authority. It can handle one transaction a second and then the business development team hits it’s goals and sells 100 more licenses. Now, this is not going to be pretty, because unless the team gets in there to make significant changes immediately, there will be a lot of upset customers.

What Does Early Optimization Look Like in LIFT?

Optimizing early means covering the items that are most likely to have non-trivial consequences. Notice this list doesn’t mention data structures – those are a topic once the significant items in the following are addressed. Identifying and addressing this short list of concerns will yield less problems over the lifetime of the system.
  1. 1.

    Make sure connection pooling is enabled to your database. This will alleviate half of your concerns with adding additional users to your software.

     
  2. 2.

    Avoid tying session state in your web application to a particular server. Putting state in a cookie or a shared system is very easy nowadays. Don’t put the session information in memory on your app server. Why? Because when you add another app server, the system will fail behind that load balance as the user state on server A isn’t available when the load balancer sends them to server B.

     
  3. 3.

    Avoid nested looping. This one can go under debuggable software, but it’s more interesting here because the consequence can manifest itself in the performance of an application. New hire hero developers love to come in and unnest loops removing thousands or tens of thousands of unneeded loops. Keep in mind that processor cores still treat loops in about the same manner.

     
  4. 4.

    Put guard statements and timers on all external dependency calls. For instance, if the application is calling out to a third-party pricing service, the response time is now up to (a) internal queuing mechanisms and thread availability, (b) the Internet, and (c) the service provider’s ability. Service providers have bad days, and they release bugs just like the rest of us.

     

Now, let’s consider the other edge of this sword. This side denounces optimizing for scale as not necessary. For instance, internal IT systems really don’t need your 95th percentile scale qualities. These systems are usually fixed. A fixed number of internal users, a fixed number of transactions, average order volume, etc.

A few years ago, I ran into a team building a website where the year over year user growth had declined for five straight years. They debated pointlessly on how the system can go from handling 2M unique daily users to 4M daily users. What for? It’s not going to happen. It wasn’t even the goal of the business! The product team was generating additional revenue by increased annual subscription rates and additional site advertising. In the end, the team delivered a good solution, but it was only for vanity. And the additional complexity will haunt the engineers who one day take over.

Performance is a key non-functional topic because performance needs management one way or another. It either needs to get addressed for scalable support in system design driven by requirements or it needs to be addressed by requirements so that additional scale and performance is not required. Ignoring performance expectations is not a professional option in a world where most software solutions are delivered over the Internet.

Your Definition of Done

Consider for a moment, in your current environment, what does it mean for a story (some work item/task) to be complete? Really, when is the work done? Not ready for testing, not prepared to be committed to source control, not when it’s ready for UAT. When is the work item “done-done”?

Having this criterion puts you ahead of many other teams in the industry, so be glad. Don’t worry if you don’t have this defined – it’s one of the easiest things you can do – it will allow you to move faster with more confidence.

Table 4-9 gives the LIFT basic definition of done.
Table 4-9

Definition of Done – Overall

LIFT Definition of Done (DoD)

1. Code has been peer-reviewed and approved by at least one reviewer.

2. Developer and tester have done a walkthrough – risks and scope.

3. Confirmed with the tester.

4. QA performed and issues resolved.

5. Automated tests locally and in the integration environment are green.

6. The story is deployable.

7. Code is finalized for our standards.

8. SQL deployable and tested.

9. All files and configuration changes captured.

10. Product owner signed off on the work item.

Let’s do a quick walkthrough of each step. All the steps preferably happen in a non-local environment, meaning the code was committed and in some dev or continuous integration environment.

Code has been peer-reviewed and approved by at least one reviewer.

When Rebecca completes writing her code, she submits a pull request with a code review for someone else on the immediate team to look. The reviewer is looking for functional correctness, possible defects, and any outstanding style issues.

Developer and tester have done a walkthrough – risks and scope.

Assign a tester to every story in the iteration, which, by default, means there are one developer and one tester per story.

QA performed, and issues resolved.

The tester has tested the story using the test criteria and other non-functional and functional criteria required for the product. No issues reported, or issues reported and then retested and resolved.

Automated tests locally and in the integration environment are green.

All test automation for the component/system/product runs locally and in the integration environment with positive (green) passing results.

If a particular story is put into the integration environment and passes its criteria, but other tests fail simultaneously, the story has failed. At this point, the build broke, and the team must fix it. The other tests may have failed from another developer’s submission, a network timeout, or other reasons. Test failure is often a point of frustration for a developer submitting a story that they know works. Still - it is the best way forward because it keeps the entire system in sync: it all works, or it’s all broken. This binary state allows engineering to be confident in either state.

The story is deployable.

There is a difference between releasing a story and deploying it. Releasing a story means that we have put it into a destination environment of our choice and confirm that someone can use it. It doesn’t mean it was successful! That’s because stories in professional environments have dependencies. There are three items to cover: the deployment process can deploy the story to a target environment.

First, the code has met the standards set forth by the team. This is knocked out in step 1 of the DoD.

Second, any database changes required for the code to work are deployed into the target environment and tested. Why? Because a missing stored procedure will fail the code.

Third and last, make configuration changes required for the story to function in the target environment. Think of this as anything stored in configuration files/databases/systems. For instance, if this story’s function requires a key’s value set to “true” in the config file, make sure this configuration gets deployed.

The product owner signed off on the work item.

Finally, we arrive at a sign-off. Let the product owner review the work in the target environment, and when they feel good about it, they will give it a thumbs up. LIFT isn’t concerned with formally capturing this decision, but your team can do it however you choose.

Write Things Down and Document As You Go

No two teams are the same, and different teams, even inside the same company, are going to operate at different maturity levels when it comes to documenting and understanding the system. Now, there is no argument that the best way to understand a software system is to write readable code. But is that enough? OK , now toss in unit tests that describe the behavior and test the code, is that now enough?

Of course not. Why?

Because we live in the real world and not some academic textbook or idealized scrum fantasy where everyone does everything, knows everything, has the same desires, and can live via tribal knowledge. Teams are spread out with people in different offices, work from home, and time zones. Information must be documented. And, as we’ve pointed out over and over, software systems are more than the code. If the build environments, deployment environments, database schemas, and terraform scripts aren’t documented in some form, then how will the team ever find efficiencies?

Document as you go.

Eliminate Waste

The concept of waste is highly subjective. The Product Manager may say that waste is any work that is built but not released. The operations engineer thinks it’s waste to conduct long releases and manual configuration. And, finally, the software engineer thinks it’s all the meetings. Table 4-10 below lists a few different views of waste. So, what is waste?
Table 4-10

An assortment of beliefs

 

Product Manager

Operations Engineer

Software Engineer

Waste

Unreleased Software

Long Releases

Meetings

Beliefs

It’s all waste when looked at from a given perspective. But turn the view, and you’ll see just how subjective these concepts are.

The Product Manager needs those meetings because she doesn’t know if the development team understands the requirements and sequencing. So, this is how it’s done in her world. The Software Engineer doesn’t choose to avoid big releases because Operations hasn’t worked with them to decompose the production infrastructure footprint. And the unreleased software is a fact of life to both Operations and Software engineering because the requirements for them change too often from Product Management.

Now, none of this is true. It’s just a perspective. It’s no different than Plato and his allegory of the cave.

In this allegory, Plato asks the reader to consider people born and raised in a dark cave, held in place to only face forward. On the other side of a wall are people who carry objects shown as shadows on the far wall from a fire burning in front. The people held captive see the shapes cast by flame and only know these shadows as objects in the world as they have no other experiences.

Next, Plato suggests that one of these people is set free and led up a steep incline out of the cave and into the sun. The sun burns his eyes, and it takes time to make out the shapes of the real world as his brain cannot believe. Given enough time, this individual prefers the outside world’s freedom and new reality and returns to the cave to tell the others. Upon return, he can no longer see in the dark, and the captives believe that the outside has ruined his eyes – and therefore, it would be perilous for any of them to venture outside. And they all prefer to stay.

Table 4-11 below lists more examples of possible waste in the product engineering cycle because they do not contribute to the software’s construction, delivery, or operation.
Table 4-11

Some areas of waste or invisible.

Possible Areas of Waste

Wait time

Build times

Debugging

Wrong tools

Meetings

Extra tickets and tasks

Looking for log files

Big change, big release

Not managing configuration in source control

Teams put these possible waste items in place because of previous experiences and a need to stay in the cave. Let’s use the example of the meeting again. The Product Manager states she needs the meetings for a few reasons: gather status, take questions, and discuss the next iteration. The engineering team (dev + ops) says this meeting is a waste because they are busy on the current iteration and don’t have questions; they want to remain heads down. Their rationale that the meeting is a waste stems from: they are actively working, and switching in and out of development is a waste of time on its own.

Last year was the Product Manager’s first proper software assignment with a different development team. The Product Manager did not meet regularly during an iteration with the team and missed several deliveries that year, reflected in her annual review. Her experience tells her that these meetings are critical, and she must push the development team. The meetings are her cave and fire – the shadows on the wall are real for her.

The lead developer tries to pull her up and into the light by taking her to coffee and explaining that finishing the iteration is the team’s most critical job, and the mid-iteration status meetings can wait. But to the product manager, skipping status meetings is dangerous. Her only experience developing software products failed without meetings. So, the meeting stays on the calendar, and everyone attends, begrudgingly.

This isn’t an attack on Product Managers – far from it. Let’s look at the lead developer.

She has been shipping software into production environments for ten years. A lot of what she has learned and internalized tells her continually reducing build times will lead to better product outcomes. That may be true to an extent, but there is a point of diminishing returns. She received rewards for reducing massive legacy applications build times from 60 minutes to 10 minutes in her previous role. It was a stated goal from her manager and did make a difference for the firm because that reduction allowed them to bring on additional teams to make changes to this application to help them in the marketplace. In another role, the architect approved of reducing build times as a breakthrough because the firm didn’t have any continuous build and integration previously.

In the current team, she and the team spend 20 hours a week trying to take an extra two minutes off an 8-minute build cycle. This building cycle has several dependencies to bring in, tests to run, and a deployment validation cycle. The team is one iteration behind, the PM is stressed, and the lead engineer won’t stop this work because this is what she knows. Reducing build times is her cave and her fire.

Neither the mid-iteration status meetings nor the intense build reduction activities help the team ship better software – so they are both wastes. The team must agree on the waste that everyone understands and fits inside of a given context. Table 4-12 displays what the team needs to identify and eliminate.
Table 4-12

Patterns of Waste

Defects

First and foremost, get the features correct and working as expected. Nothing erodes confidence and velocity like excessive defects.

Wait Times

This is the time that a team member spends waiting for another. This could be waiting for acceptance criteria, waiting for testing to finish, waiting for architecture to finish some service, etc.

Excessive Motion

Too much motion in a software team is often manual configurations, by hand deployments, manual testing, or multi-step environment setups.

Overproduction

Overproduction happens when a team creates code and services they do not need yet. It can also encompass overoptimizations, like the data structure hand-built to handle N varieties of widget types when there are only three and have been three for the last 20 years.

Underutilization

Poor utilization will occur when subject matter experts are ignored by clever engineers, QA team members aren’t involved in planning, or developers aren’t shown the big picture and treated like widgets. There is so much that happens when our people aren’t used to their full extent and books are written on this every year.

Deploy

After going through this evolution a couple of times, a team finds its natural rhythm. And underlying the team’s rhythm is the constant drumbeat of “checking-build-deploy” of continuous integration. Now, CI is a standard and table stakes operation, but cannot be left out. After each developer code commit (check-in/push) to source control, a build process must kick-off, run any available tests, and deploy the bits to a development environment.

This environment is how the product, QA, and the entire development team see the fruits of everyone’s labor and avoids the “works on my workstation” syndrome.

Activities Summary

A number of these activities are to be constructed the first time through the evolution and then used over and over.
  • On the first time through evolution, create Sprint duration and lay in sprint calendar timebox activities.
    • If stuck on a two-week concept without success, try the three-week calendar in this chapter.

    • Stick to the activities inside the timebox.

  • Address all non-functional requirements, in the same way, each sprint.

  • Defensive programming, logging, debuggable applications, and performance.

  • Adopt and use anti-corruption layers like your product’s life depends on it.

  • Adopt and use the LIFT definition of done.

  • Document critical architecture, functions, and decisions as you go.

  • Deploy to a development environment continuously.

  • Eliminate waste. Every set of activities, including these, has waste. It takes some focus and some grit to eliminate the waste, but doing so will save stress, time, and project injury on the backend.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset