Software development is plagued with bugs, tech debt and long delivery cycles, costing money and providing no value to the customer or the business - and that's all before the first release! The eXtreme Programming development practices can significantly minimize, if not entirely remove, these issues before and after release. These are well known practices, but not as commonly applied as their value would suggest. We strictly applied XP and other well known software development techniques allowing us to reach feature parity with our other mobile apps in half the time with a team half the size. Feature parity in 25% of the developer hours.
Our experience with the practices clearly demonstrate that the practices improve the product and experience of software development. It’s rare to get a 1 to 1 comparison of feature development time for multiple techniques. We got this opportunity and had the knowledge of practices to take advantage of the situation.
It's unfortunately very different than the code we typically see in the industry. Change is hard, change is scary. While adopting XP practices consistently delivers working software better and faster, teams don't tend to adopt XP because it's a big change.
Imagine this scenario: Nine months into the development of native iOS and Android apps; you’re tasked with delivering the same feature set as their initial release on Windows. All three apps need to be released in the same quarter, seven months away. You only have a budget for three engineers to accomplish in seven months what two teams of six would normally take fourteen months to do.
Can you deliver on time?
With XP and strictly applied Object Oriented software development practices we were able to deliver the same feature set as iOS and Android with only 25% of the developer hours.
Insurance companies want things to be as predictable as possible. The business model is around their ability to predict things. Even if the unknown can deliver better results, it’s not predictable and getting buyoff for new things is not easy. I enjoy going against convention and did something different.
I’ve always strived to find ways to do things better. What has worked before isn’t always what will work best and requires experimentation with new ideas. This helps find what works best for the current situation. This is to help avoid the pain we’ve experienced in the past.
One of the pains I’ve had in the past was experienced by the iOS and Android versions of the mobile app. Both product teams had modules they needed to rewrite - before either was released. I had my experiment with a new style of how to write code to see if I could avoid that.
3. STARTING BETTER PRACTICES
I was hired at Premera in October 2016 to be part of the mobile application update efforts. After years of ignoring their mobile platforms, Premera started to rewrite their iOS and Android apps late 2016. Premera also has a Windows Phone app, but there isn’t a large enough market to focus efforts on this platform initially.
In December 2016 I was fortunate to take part in training by Fred George. His Object Oriented BootCamp was invaluable for me towards developing some of the technical practices that enabled achieving Feature Parity in 25% of the Developer Hours. Fred's training crystalized development concepts I had some vague notions about. We called these “Fred Georgeisms”.
- No Getters and No Setters. This was something I was getting close to, but to practice it and see it so clearly drastically changed how I think about writing code.
- If only as a guard clause. Use ifs to protect the method, not have conditional complexity in the the flow.
- Switch and Else are always evil. It’s not quite that they should never be used, just that they should be limited to the edges of the system.
- What is your job? I consider this Fred’s form of Single Responsibility. It forces thinking about the object in very different ways; ways I can’t do very well.
- No Primitives. It took me two years since the training to actually understand how to avoid primitives in my code, but I would have never understood without.
These practices I learned from Fred are old practices - yet new and confusing. Others who’ve been through the training struggled to put them into practice. A week of training isn’t enough time to learn how to do these practices appropriately. I knew I had to work with the ideas and concepts in more ways than development at work will give me. I had to get them into my head to allow me to use them in the future.
Without using these practices, I’d forget them. These are not practices I wanted to forget. I started blogging to record my efforts working with these practices and what I learned about using them.
After the training I spent nine months working on the APIs the mobile apps would call for data. These nine months taught me the XP practices. The server team used FAST Agile (http://www.fast-agile.com/). A colleague and friend, Ron Quartel, developed this process from his years of consulting and conference going experience. It's been the best process around software development I’ve ever participated in.
The server team only had a small number of XP minded individuals. There was a lot of resistance from other engineers towards changing their approach to software development. They’ve been successful doing it that way for their entire career. An unfortunate reality that isn’t uncommon when trying to bring in different technical practices. FAST Agile promotes the technical practices of Extreme Programming. The nine months working in FAST Agile taught me a lot about XP practices and some of the failings of letting people make decisions around how they're going to do their work.
In July 2016 I was given the opportunity to lead the rewrite of Premera’s mobile app for the Windows Store (UWP). I absolutely wanted to, with some conditions. The biggest was that I was empowered to dictate the style the code was written in and the process we used. The technical and process practices the team would use are critical to the type of success the team could have. I wanted to really see how the XP practices could impact a project without detractors impacting the quality of the code.
We had to hire the other two people to work on this product. The biggest challenge in hiring for this team was finding someone, from the small market of people, with UI experience for UWP apps.
3.1 Beginning with Continuous Delivery
Before we found anyone besides myself for the team, I started on some infrastructure for the project. One of the XP practices I’ve had drilled into my head is Continuous Integration/Continuous Delivery (CI/CD). How can we get the product into our customer’s hands as early and frequently as possible? Not only early and frequently, but how can we do so as realistically as our actual releases would be?
I investigated the Developer Portal for the Windows store and found they have “Test Flights”. You could have releases constrained to specific groups, emails or promo codes. This was perfect for delivering to a small group of people to test with!
Knowing the approach we could use to realistically deliver to our users I started getting things for the CI/CD set up. The first things that would be run through the CI/CD would be default projects from Visual Studio for the production code and test projects.
Getting the product to build and release using Azure DevOps was very similar to the processes I’d set up while working on the server team. Most of the time was spent testing to ensure we deployed as a new product. Once everything was configured I was thrilled with the success of the CI/CD working. Being able to make a commit and have the product deployed to a test flight in the Windows store!
I was really disappointed when I found out that all releases, including those for test flights, had to be reviewed and approved through the Windows store approval process. Reviews could take days, and we needed to have new versions available quickly after committing code.
We were able to quickly pivot and use HockeyApp (now Visual Studio App Center) to deploy our releases. The iOS and Android teams already used HockeyApp as their testing distribution platform. We were now able to quickly and frequently get working software to users for feedback.
Through a few weeks of interviewing there was only one candidate that came in with UWP experience. No pair programming or Test Driven Development experience. Nothing even close to the practices the project was going to use. I wasn’t and still wouldn’t be concerned with that because people can learn the practices. He was very agreeable to the practices in the interview where we had some good discussions about them. He’s been fantastic and I’m currently lamenting that we’re not on the same team anymore.
3.2 Ruthlessly apply good practices
Having established a build and release pipeline and the new engineers for the team coming onboard, development work could begin. I'd finally get to see the practices I've glimpsed working so well applied full force on a code base.
I’d spent six months exploring the technical practices from Fred’s Object Oriented Bootcamp. I felt I had a pretty good grasp on how to apply them.
A coworker asked me if I’d read a book he thought looked a lot like what Fred George had taught us and showed me Elegant Objects Vol 1 by Yegor Bugayenko. The cover mentions “No getters, no setters” and I knew I ordered it that day, August 2nd, 2017. On Saturday, three days later, I voraciously consumed the book. I consider Elegant Objects a “How-To” for writing the type of Object Oriented code Fred was training us for. I started to explore and experiment with the new ideas it presented. The training with Fred helped me understand a lot of the best practices in software development. Reading Elegant Objects gave me an understanding of how to write code that forces the best practices into the code.
- Keep constructors code free. Don’t do any work in the constructor of a class, none.
- Encapsulate as little as possible. This gave me a hands on keyboard approach to keeping my classes smaller.
- Method names are what they return. There’s something for void and boolean methods, but if it returns something - name the method what it returns.
- Immutability. Along with other practices Elegant Objects provides, immutability became a pretty easy thing to do.
- No static methods. This was one I was trying to find a way around. Util classes got out of hand at times.
- Don’t accept NULL arguments. Don’t allow your code to be passed a null. Don’t check for null, never pass them.
- Don’t instantiate objects outside of constructors. This practice is what made interfaces finally make sense.
- Never return NULL. The simple implementation of this is “Use the NullObject pattern”.
- Be final or abstract. The re-iteration explicitness around how a class can be instantiated make it click.
Without Fred’s training, this book wouldn’t have been a guide for me. Without Elegant Objects, I would have struggled with implementation details and not achieved the success with the project that we did.
With this even better understanding - How I do help others learn it? How can I make sure the team, who had not had Fred’s training, gets what I want to do?
3.3 Team Learning Culture
The first thing I wanted to do was try them out with the team. One of the examples we did a few times was calling an API and displaying a value out of the JSON response. This is pretty core to the app, calling APIs and in some form, getting that data displayed on the UI. If we could get an understanding of that flow, error cases included, while applying the extended set of technical practices, we’d at least have a pretty good shot at it for the whole project.
I tried to build on the ideas as we continued to work with the practices. We’d use the foundation from our API calls to authenticate against our login server.
The time to spike, explore, and help the team get a better understanding took a couple weeks. We started to write product code late August 2017. The first commit for actual code was August 18th 2017.
Our first feature was logging into the app. We wrote the happy path in an entirely procedural fashion. Just the happy path with a lot of hard coded URLs and values. The method did a lot; intentionally. I could identify about twenty different behaviors this single method was doing. Starting with a procedural method doing about 20 different things, we’d apply the technical practices to refactor the code. Ruthlessly and repeatedly. The authentication process ended up being 70 classes. This includes all the networking classes that would later be used by the rest of the app. For our first feature logging into our app - 70 classes.
I wanted to see the success of these practices outside of my involvement. After a few weeks I felt the engineers could continue to follow the practices pretty well. The first commit without my name on it was September 8th, 2017. I kept an eye on the code and worked with the PO and PM. This was still a project I was leading. In addition to keeping an eye on commits, I’d experiment with what the developers had done and I’d bring up suggestions and ideas for discussion.
3.4 Paired Learning
Some of the greatest improvements to the code were the days I’d come in with an idea for refactoring and through explaining and demonstrating it we’d have a discussion about it. Disagreements were the best part of the development process. We’d each have and show our ideas. We then discussed what we liked and didn’t like.
We’d try variations of each other’s idea pulling in pieces of our own we liked and leaving out the aspects we didn’t feel fit right. These variations led to more discussion and more variations until a better solution formed from both of our ideas and the discussions we had. This time spent exploring variations of implementations and how to improve an area of the code often allowed us to understand other, similar, areas of the code better. The tests around the classes getting refactored always simplified. We’d reduce the behaviors and complexity in the class resulting in fewer tests and expected behavior easier to understand. This allowed us to evolve the patterns we used to simplify and remove abstractions making the code even more maintainable. The time spent discussing the code easily saved an order of magnitude worth of work from the discovery of a simplified pattern that had a broader reach in the code.
Once the login flow worked we had to go somewhere so we landed on a wonderful empty page. This enabled us to start writing our UI tests. This lead us to a great discovery: our build machine couldn’t run UI tests. Not running UI tests was not really an option. We needed to run tests.
3.5 Software was always in a working state
A key to our success in the development time of the UWP app was our adherence to the practices that help the product stay in a known working state. We need UI tests. We were not going to accept them only being run when we remembered to run them locally. We set them up to run as part of the build, every build. We would run the UI tests locally twice a day, before lunch and at the end of the day. This was for us to be able to monitor the UI tests running and know they were still working for the correct reasons.
With the UI tests in place for our first UI, we didn’t start developing the next screen the user would see. After logging in the user will see a screen with a log of information summarized. We talked about how we should approach this. My view is that since what we want to display on the dashboard is a summarized form of other bigger features, we should build those big features first. If it works to componentize and reuse code/widgets for the dashboard, we save time. If not, we develop new and lose no time.
This was not a big design session. We sketched our discussion on our team whiteboard and then went back into code. This was one of the early steps of an app wide emergent architecture. We know we need the functionality in two places, we’ll build it in one keeping in mind what might work to be extracted for the other. I prefer to build the most complex stuff first. It has the most use cases and will drive more general solutions if they can be shared. Reusable code/widgets is a boon to faster delivery.
We built out our next feature, Find Care, with the same process we did for the Login Page: procedural blocks of code we’d refactor into collaborating objects. With our knowledge of what we had from the Login Page we didn’t reinvent things we already wrote. An example is that we had a pretty general case networking layer even though we prefixed EVERYTHING with “Login”. The only use case was for the Login feature, and to help us conceptually not try to make it general early, we named it specifically.
As we found we could reuse modules we renamed them to be general purpose—often with no code changes.
When we finished the Find Care feature we returned to consider what we wanted to display on the dashboard.
As we spent a lot of time in the refactor step of Red-Green-Refactor, our functionality typically had a narrow interface and deep functionality. My favorite example of the simple interface and deep complexity is our login module. The module exposed a single class with a single method. The functionality it encapsulated was comprised of 70 classes. This is having a narrow interface with deep functionality.
Our Find Care feature had a fairly well encapsulated widget for the search functionality. We were able to use it on the dashboard with very little work. I attribute this wholly to our practice of emergent architecture. Get it working, get it working for more things. Refactor it back to simplicity.
The Find Care widget is a great example of our application of refactoring.
Once we got the Find Care page done, we put the widget on the Dashboard and made hacky inline changes until we got it working. Absolutely no consideration for elegance, our focus was to get a dashboard widget to work, without breaking the UI of the Find Care page. This focus on adding new functionality, without breaking the existing functionality, follows a lot of the process we use when we do Test Driven Development.
- Red - Write a failing test.
- Green - Make the test work; Quickly. Committing whatever sins necessary in the process.
- Refactor - Apply the technical practices to remove the sins from the previous step.
We didn’t do Test Driven Development on the UI, but we kept the spirit behind it. Our Red was the lack of a widget on our dashboard. We'd have to get a working dashboard widget to be Green. Committing many sins in the process. We would modify the code with an IsWidget flag for the functionality we wanted the dashboard widget to have, even if the functionality existed for the Find Care page somewhere. This ensured we focused solely on getting to our Green, a working dashboard widget. We isolated the dashboard widget changes to ensure we kept the Find Care page working.
Once we got the different behavior identified, we worked to simplify the code. We refactored everything common between the Dashboard Widget and the Find Care UI into an abstract class. This abstract class contained all of the UI definition and the common behavior, which was most of the behavior. Extending the abstract class were the Dashboard Widget and Find Care Widget, providing just what was unique to each.
The dashboard widget is an example of emergent architecture that we applied in our code. We didn’t design the architecture or class hierarchy of using the same code on the Find Care page and the dashboard widget. We wrote the code we needed to exist and then refactored it into a sensible architecture. Emergent architecture allowed us to delay complexity in the application until it was needed and do only what was required. This process produces the most concise and minimal architecture I've ever experienced in a product.
As we continued development we held bug bashes every couple months. This time for co-workers to try to find bugs in the application had proven to be an invaluable exercise for the iOS and Android apps. It exposed a lot of bugs in those apps before our users would have a chance to discover them.
In our app though, no defects were found. An occasional UI oddity around the map functionality we had, but we had no defects for logic and expected behavior.
Prior to release we worked with a security firm to perform security testing on our app and use of services: No defects.
There was one suggested change though. We had the service endpoints, the APIs to call for user data. hard coded in the app itself. The suggestion was to remove it from the code and put it in a configuration file that was injected during the build process. The build file would be part of the app binary when deployed to the user.
The team discussed the suggestion and other options. We decided that a built in configuration file was an easier exploit than having the URLs in the code. We understand the security need to bring URLs out of the source code. Our solution was to put all that information into a config file and pull it from our server. This is important because we made this change the same work-week we released into the Microsoft Store.
I consider moving the end points from the code to a server based config file a pretty hefty change. Very non-trivial impact to the fundamental functionality of the application.
We had absolute confidence in our code and process that we had no concerns making a significant change days before release. We made changes up to an hour before we released.
3.6 Results of our practices
Where did the Object Oriented practices we used to write our code and the XP practices we used to develop the product get us? Pretty far, pretty quick.
The iOS team started coding in October 2016 and delivered to the Apple Store January 2018; 15 months of development with a team average of 6 developers.
The Android team started coding in November 2016 and delivered to the Play Store February 2018; 14 months of development with a team average of 6 developers.
We pushed to the Microsoft Store April 4th 2018 after 7 months of development and no more than 3 engineers. Half the time with half the team.
After the initial release of the UWP app, development was stopped for Windows. There’s not enough users of the UWP app to continue further development. The UWP team moved on to create Premera’s award winning Scout Virtual Assistant.
The practices we applied and refined during the development of the UWP app were used to develop the Virtual Assistant app. With the huge reduction in development time these practices afford us, we were able to release in just two months. The Virtual Assistant product has been going strong in development for over a year with zero defects in the code.
We developed Premera's Virtual Assistant using the same XP practices. As a result the initial release was just two months after development started. In over a year of active development on the Virtual Assistant there have been zero code defects.
I’ve moved on from the Virtual Assistant team to apply these practices to legacy code. It’s different, but with the correct refactoring the practices can be fully in place. Code that has undergone this transformation is no longer legacy code. It becomes safe to make changes and they are easier to make.
4. WHAT WE LEARNED
The biggest thing that came out of this for me is that the practices can provide quality and speed improvements beyond anything I’d imagine. I took on the UWP app for Premera without any expectation of being able to accomplish what was being asked. I was given 3 people and 7 months what a team of 6 took over a year to do.
I expected I might fail. Out of this I was going to see what the practices could do. Fallout from failure was for future me to deal with. This acceptance of how I was being asked the impossible freed me from the desire to take shortcuts to “hit the deadline”.
I’d never really done emergent design on a product before. This was the first time I got to employ it. Emergent design simplifies, enables, and speeds up development. You’ll never write a piece of code you don’t actively need.
I learned and gained understanding about Object Oriented software development practices that I couldn’t have comprehended before. I’ve taken what I’ve learned over the course of years and developed my own set of evolving rules for how I write code (https://quinngil.com/uobjects/).
- Never return your data
- Be Immutable
- Interface for Behavior Contracts
- Abstract 3rd Party Code
- No Public Statics
- If Only as a Guard Clause
- Switch and Else are always evil
- No Nulls
- No new inline
- Extract Cohesion
- Composition, not Inheritance
- No primitives or enums
- No logic in constructors
I found that these rules simplify code beyond anything I would have imagined. Applying them all is hard and I worked to find which did the most for me. No Getters. If you want to do anything new in how you write code, never return your data - No Getters.
I learned I don’t really understand business. No one was beating down my door to learn how to do this. Delivering the same feature set in half the calendar time and … no one wants to learn the practices that enabled this? Ignoring the smaller team size; “just” a 50% improvement. Faster delivery with higher quality and no one’s interested? I expected the business to jump at the opportunity to deliver more maintainable code faster. This has not been the case and has been a frustrating realization for me.
Change is hard. A lot of books have been written around this lesson learned. Even for something that’s objectively better - people aren’t jumping at the chance to change. It’s hard for a huge number of reasons which many books have been written on. This realization has gotten me reading many of the books written on change so I can hopefully help people write better software.
After this project, working with others and on other code bases I’ve found that most XP practices are skills the software industry lacks. Even though XP practices continue to enable teams to deliver faster and better quality software. In particular, the practice of refactoring is a skill the industry doesn’t have, at least not at a level to effectively remove complexity from code. This means that as an industry, we lack the ability to maintain the software we write. Adoption of XP practices would help the software development industry move forward in speed, quality, and sustainable software.
Steve Kuo, Ron Quartel, and Todd Manning brought me onto an XP team at Premera which started me down an amazing path, that I continue to this day. The collaboration and conversations were crucial to better understanding of the practices. Fred George for challenging everything I thought I knew about how to write code. Neil Lazo, Kara Thorson, and Shannon Layden for trusting me with the opportunity to do the impossible by doing it differently. Brian Chesbrough for embracing being part of doing development differently. Paige Watson for helping me remember to not be the egotistical ass, about code, that I was before.
This paper could not have come together without my experience report shepherd Johanna Rothman. Her keen insights, questions, edits, and suggestions helped it be what you see today: Thank you, Johanna! I would have never been able to do this without you!