Let’s examine two questions common to software development projects. First, Can complementary tasks create adversarial competition? Second, when to bring more programmers on to a team. The answers depend on the mission and an assessment of what causes delays. As the team narrows the focus, we “begin with the end in mind” discovering objectives of the applications.
"The Soul of an Internet Machine". This show explores the intersection of business and technology and the internet.
6 | A HEAVY LIFT
Our Electrotest project started fast. Dimitri, our project leader, established some rules and objectives. We had the required timeline describing deliverables and their related dates. That’s project management right? Top marks for a full color Gantt chart with task spanning vertically down the list and due dates spanning laterally.
February of 2022 showed us a gradual transition of our project slimming to specific goals that will benefit the organization. We shall generate and deliver invoices by summer. Success will require capturing data from legacy systems and generating invoices using United Code’s APEX Office Print (“AOP”). And before going-live with this module, we will be communicating with multiple external systems transmitting invoices to a Cloud-based accounting system and to Belgium based firm that distributes invoices. We need perfect customer data. Additionally, we need perfect service order data. With this foundation, we can then generate invoices in Dutch and French in accordance with the client’s preferences.
We had already developed the application framework using two Oracle APEX applications. The majority of users will engage only with the Customer Service application. Managers and designated staff can jump over to the Administration application to update lookup tables, adjust user’s credentials, and view behind-the-scenes data such as errors, logs, and the like.
We’ve been building credibility and honing the look-and-feel of the application. In February, a new member of the team presents the requirements for a new application for exactly one division within Electrotest. The division that inspects equipment called “lifting” wanted a portable application to aid their inspectors performing their duties. Lifting equipment includes material handing equipment (or MHE). These are devices like forklifts, cranes, elevators/lifts, and related equipment. We, the developers, guess that the legacy system for performing inspections, did not meet the needs of this division.
We should greet an expansion with joy and excitement. It is the work that we do. Instead, the project came in with deadlines, requirements, and pressure that created competition with the core tasks. Teams can accommodate competition for resources by adding people and expanding our scope.
When software development teams add people and add scope our profits tend to increase. The cost to the client goes up. Our lifting project, to craft a bad pun, would be a big lift.
How can two complementary tasks find themselves competing with each other?
What is the risk of adding people to fill gaps and move faster with development?
Let’s explore both of these questions.
Can complementary tasks create adversarial competition?
The client determined that our priority for the spring is improving their invoice process and standardizing a manual process within our Oracle APEX application. In the early weeks of this project, we all called our application “middleware” as a means helping corporate users get comfortable with yet-another-solution tiptoeing into the office. The gradual drifting from the benign term “middleware” to a targeted project focused on generating invoices required all of the building blocks for invoices to exists within our Oracle Database.
The users managed the data in an on-site legacy system we codenamed “DAX” – which likely incorporate multiple legacy systems poorly linked to each other. Dirk, who served as our team’s first business analyst, provided us with 136 data table definitions. He created these tables to ensure compatibility with DAX. We need to capture what DAX had and to perform that task, our data structures must resemble the remote system’s data structures – as a general rule.
The DAX project faced its own mortality. I mentioned in a prior episode, that the “unique” order number recycled after 100,000 orders. This recycling process meant that order numbers were not unique. DAX created rules that did a poor job of validating data. The DAX users did a terrific job in bypassing these poor rules. Meaning we had hundreds of customers with the name of a dot or a slash or a series of dots or a series of slashes. Customers were duplicated within the system. DAX failed to accommodate the complexity of addresses. Not only does Electrotest have to accommodate the standard issues with physical address and mailing address. Client’s may have multiple locations. Electrotest maybe request to inspect a client’s crane or forklift that is located at temporary site.
DAX failed to accommodate the complexity of managing contact people at client sites. Seems like they had one contact name and phone and email. Then someone decided they need another for accounting. Then another for technical contacts.
The database within the legacy system did not remain within the “normal forms” that keep tables efficient. In the customer table, when needing a new address, the developers added a new field for a new address type. Similarly, when needing a new type of contact, they added a new set of fields for the new contact type. Here is the email and phone and name of the person who is to get the inspection report. That’s three new fields. Oops, we need to track the invoice contact separately from primary contact. The developers added three new fields for that new contact.
The data normalization discussed in Episode 5, “the Color of Language” suggests that customer profile data remain resident in the customer table. Then we create a table for the addresses. One customer can have multiple addresses in a parent-child relationship. The addresses can be identified by their purpose. Here is the invoicing address. Here is the official, registered, or primary address. Here is the address for their warehouse. Similarly, contacts should be stored in a separate contact table giving the users freedoms to add and remove contacts easily. In a small company, one contact could be designated as the primary contact, the accounting contact, the reporting contact – all without duplicating data.
The database system underpinning DAX had the capability to host properly normalized tables. The human being designing it did not anticipate growth and changes. The human beings took a few common short cuts. These short cuts seem easier and get jobs completed faster during the early moments of a project. As software matures, get a few grey hairs, new users come in, business requirements shift, the software cannot flex sufficiently to meet the demands.
Easy for us to stand back pointing or joking about these historical mistakes. Instead, our team must embrace these challenges. In order to generate an accurate invoice, we require complete customer profile data. No system can generate an invoice for a customer named dot-dot-dot or slash-slash. We looked into the legacy data and found email address that said: “Fred”, missing the at sign and qualified domain name.
Our mission included creating a digital connector to the legacy database. Then we were required to import the customer data transforming it to something real. Honestly, we cannot create a customer when the only name we have is dot-dot.
How does a team sort good from bad? It took months. It took weeks to create the tools that imported from the other database system. Likely not weeks to create the tools, but weeks to get them right-ish. “Perfect” ought to be the objective, it remained elusive. A philosopher once said that one cannot make a silk purse from a pig’s ear. The silk purse fell beyond the scope of our time and skills. Bad data are bad data. It only improves when human beings manually improve the data.
We tried.
In one customer record from DAX, we had four contact names. One or more of the names maybe empty. One or more of the names may be identical. One or more names may be identical but the email addresses are typed differently. Maybe both email addresses were valid, maybe only one, maybe neither. DAX did nothing to ensure the quality of these data.
In past projects, once the development team figures out how to import data from the legacy system, we practice a few times. Then we make one final pull followed by going live on the new system.
With Electrotest, we depended on the external system. Our new application had not created all of the modules and features of the DAX system. Therefore, every new customer came from DAX plus their contact people plus their addresses. Every hour of every workday we have to pull data from the legacy system.
What could possibly go wrong?
Nearly everything.
We had to write logic that looked in each contact name and determine if it was good enough to be real, or do we toss it out into the bin. Same with addresses. That’s our job. That’s just work.
We needed to know if a customer was a duplicate of an existing customer. The DAX customer reference number was not precise enough for that. To solve problems in DAX, users occasionally added a customer a second time on purpose. Then sometimes, they added a duplicate because they could not find the original customer.
One of the crazier problems we faced from pulling data from this remote database is that their database defined “null” differently than our Oracle database. In Oracle, “null” is the absence of data. Think of a number field. A number field could store a zero, or a one, or Pi. Zero is a real value. Zero means zero. You can have an invoice with zero euros due. Zero is not the absence of data, it is data. Null is the absence of data. We have a variable type called “boolean”. The casual definition of boolean states that it has two values. It is either true or false. In Oracle, our boolean variable has a third state. A boolean can be null. Null is neither true nor false. Wrap your head around that. If you add null to anything, you get null. If you concatenate a series of letters to form a word say: H-E-L-L-O for “hello”, you’ll do well. If one of those letters is null, then the entire word is null. Null is null. When we pull null data from DAX, its null was not null, nor was it anything else. The Oracle database and the other database were not compatible at this level.
As we pulled these messy data into the new system, we attempted to normalize the data. We put the contact information into the contact table. Similarly, we put the address data into the address table. We executed this process again attempting to improve the quality of the data while reducing the risks of duplicating data.
At present the staff at Electrotest depend on the legacy system(s) to generate their service orders and to generate their inspection reports. To create a service order in DAX, they must have the customer existing in DAX. Through the spring and summer, the new application required customer data and order data to come from DAX. Getting the importing process right involved repeated efforts of trying, modifying, and trying again with hundreds of thousands of rows of data. Failure tended to mean re-importing the entire data set.
DAX orders often had a value of zero or one cent. Sometime the DAX service order had a value of minus one. These data points reflected user’s attempts to work around or through issues within their legacy system. Now these same orders with incomplete and inaccurate data came into our glorious new system. Our system still had that new car smell and a shiny exterior.
I should never take offense by loading a new system with junky data from an external system. Maybe I did anyway. If I had a wand from Olivander’s shop on Diagon Alley, I might have done better. The task tried my patience.
But to deliver an invoice, we needed these data. Our system did not yet have the means of creating orders.
That returns us to the question, how can added scope create negative competition within a project?
The users create service orders within DAX. The order lines describe services performed, and travel fees. The order lines had the necessary values to calculate the invoice value. DAX order lines were missing a unique code that informed us which inspection service had been performed.
In most systems that perform such task, one invoices for a standardized item. Systems like this have an inventory table. The inventory table may also include service items. We create linkage between the order and the goods /services ordered. We validate the goods or services against another table. The pricing gets standardized. The descriptions and product/service names are standardized. Maybe there are some rules in there. This service is taxable. This service is not taxable. This item can be discounted, but this item cannot be discounted.
We create a table for goods-and-services that stores the profile for each item sold (whether a product or a service). If your business primarily involves selling goods, the table tends to be called inventory. If your business is primarily selling services, then maybe we name the table service. Electrotest primarily sells inspection services. The DAX system did not appear to have a table that defined these goods-and-services. An inspection for a petrol pump or a passenger lift did not have consistent identifiers, codes, or primary keys. The DAX order included a description of the service, and it did it in only one language.
The client, Electrotest, provided us sample data for the inspections they perform. We had the Microsoft Excel spreadsheets that contained descriptions and pricing information. Excel spreadsheets are horrible databases. While working through the DAX import and invoicing process, we strove to create data structures that would store these inspection services.
If you picture building blocks or Lego® bricks as analogy, our system had a building blocks for customer, contacts, and addresses. Imagine placing a brick down for contacts. On the same foundational level, place the address brick next to contacts. Snap the customer brick on top of both address and contact. We now had building blocks for service orders coming in from DAX. Now snap the service order brick on top of the customer brick. Oops, that’s not right yet. Part of the data needed for service orders includes the “service” data or inspection data. Lift that Order brick up and adjust to the right. Half of that brick has support. Half of that brick does not have support. A service order stores data about the customer and data about the services performed. Therefore, half of that order brick float over empty space. We have no service order data yet. We have not yet built the building block that defined the inspection services in a uniform and complete manner. We struggled to capture the complexity needed for the pricing process.
The invoice brick can snap on top of the order block. Nearly all of the data needed for an invoice comes from the service order. The invoice brick snap in there fully supported and apparently solid. When snapping these imaginary Lego® bricks together, we should discover we are building a solid structure and one without holes. Holes in the wall, represent holes in the data. The service orders could not be validated against a standardized set of inspections. We could not find standardized codes (there were none). We could not validate pricing (it was not yet standardized). The service order building brick shows a gap below it.
We had no table that accurately and completely modelled their service data. Without that, we could not improve on the messy data we get from DAX. We would fail in the same way that the DAX system was failing.
Back to the Lifting Project
The Lifting team at Electrotest wanted a quick solution to aid inspectors performing inspections in the field. The client hoped we could blast together a system as quickly as we stood up the framework. The inspectors would arrive at the client site. They would select the customers asset, such as a personal lift or a forklift. They would inspect it. We would offer quick-codes for the inspection on a dial-pad with the top twelve codes.
Seems easy! The client knew we could create a tablet-based application with user security within a few days. We could have all of the client data and be ready to go. In fact, we had definitions for the data we needed to store about customer assets (forklifts, cranes, escalators, etc.)
But what is missing?
We have customers and their contact people. We know their addresses. We can create an inventory of their assets. We do not yet have definitions of inspection services nor their prices, the very same hole or gap we face with the service order data. Internally, our technical team faced pressure to ignore that gap then jump ahead. We were asked, “Can’t you catch the quick codes and get the inspection completed.”
Making myself unpopular yet again, I had to ask: “To what end?”
Performing an inspection for a customer meets two objectives. First, the customer gets an inspection report from a qualified inspector. Second, Electrotest invoices for those services. Every inspection generates an invoice and revenue.
In our case, our building had a huge gaping hole. We could not step from customer and customer asset to the creation of a service order. We had no inspection service codes. We had no pricing process. We must solve these problems before we create a cool dial-pad app for an inspection report.
We must push forward to resolve the issues in front of us. Accepting a quick project that will nearly immediately slam into the same conflicts does us no good. We will fail in the same way the legacy system appears to be failing.
Sometimes doing A before B is required. Sometimes pulling trousers on before slipping your feet into shoes is simply better, faster, and more efficient.
For the long-term viability of the software our team was building, we should not leave holes in the structure. And where possible, we should reinforce the integrity of the data with good relational database rules. Let’s avoid past mistakes.
One discussion involved bringing in more programmers, folks new to the team. They will take on this stand-alone module. In support of that statement is the fact that the field service module does run separately. It will be on a hand-held tablet used by the inspectors for exactly one inspection domain. The team doing inspections in the lifting domain execute their practice differently than the other domains. Can’t our team just bring in new developers and set them on that task.
We faced conflicting goals of the lifting modules. Our assigned and core mission for the spring focused on generating invoices from legacy data from the systems we codenamed “DAX”. Following that process, we expect to solve the problems related to the definition of inspection data and how pricing is done. From there, we can create service orders from our native data. We would fill the hole beneath the service order Lego brick. From there, we would barely have to touch the invoice module, hopefully.
Both projects face the same core issue.
If you add new programmers to create a stand-alone module, we risk losing the cohesive structure we have been building. They would create a solution, filling the gaps for the lifting team, and the lifting team may get an inspection module that generates inspection reports. But where is the invoice? Where is the service order? Will this short cut solve long-term problems? Or would we be creating a parallel system?
In the 1980s and 1990s, organizations and developers agreed that nobody builds anything until one-hundred percent of the requirements are understood. Project died horrible deaths from this process. People tend not to possess the ability to see that far in the future, examine the intricacies of each module and how they inter-related. We gather requirements. We write a detailed design document. Then we write software that meets the design document.
This process fails. One cannot grab all requirements all at once for a large and multi-phase project. We can try. That’s not necessarily the failure point. The failure stems from the objective. Software developers got happy points for ticking off tasks within the design document. The developers scored completeness by comparing their products against the design document. All too often, the design document did not capture what the client needed.
The focus of building great software must be building software that the solves the client’s problems today. Nobody cares how well we scored against the design document. That’s the wrong objective.
Human nature, and financial budgets, enhanced the sense of failure. The client paid for the requirements gathering process and drafting of a comprehensive document. The client paid for the comprehensive design document. The client wants software and we have delivered two massive documents. Clients do not read these massive documents and see the future issues. Finally, the developers start writing code. Their target, our target, is the design document as it was written.
If the goal is to develop software, then develop software. You can show the intent of application design better with an application than with a picture of an application surrounded by words written in English. Users and clients have a super easy time point to elements asking for changes.
Electrotest gave us their corporate colors. Their dominant colors are red and grey. Create the left-hand navigation menu in their corporate colors. Historically, our team used colors on buttons to help communicate. Red buttons deleted. Green buttons added stuff. Blue buttons did something affirmatively such as save, next, or whatever. Grey buttons were benign. Grey buttons carried you backwards or cancelled a process. The blue button served as the default button, or as Oracle APEX calls it, the “hot” button. Normally a page has a hot button that saves the data and a grey button that lets you back up.
Red menus and green buttons looked too Christmas-y. The blue buttons with the red menu looked horrible. We went through several iterations, sometimes sharing screens to build consensus. “What do you think about this?” We collaborate on decisions using the software we are building. We are not looking at a stupid piece of paper with a picture of what it might or could look like. We are doing it and doing it together in the software we are building.
Software designers and architects learned to shorten the cycle between requirements, design, and development. Don’t design the entire system in one go! Do it in continuously evolving stages. We must listen to the client, do something, then get feedback immediately. Often, when a client says we got it wrong, what we learn is that neither of us understood the complexities involved. We did what the client wanted, so that should be right. When looking at it in context, it isn’t right. We see new requirements, new complexities. We both look at the challenge together, acknowledge it, and solve the problem.
Except not always.
Sometimes, the developers hear the customer say: “This isn’t right.” Then the developer says: “I did exactly what you told me to do.” The developer wants the customer to know the customer provided the wrong information or incomplete information. Some developers would demand a change order and new instructions. I step into that trap once in a while. So easy to do. I worked hard doing what the client wanted. I used your corporate colors, blah, blah, blah.
When I behave like that, I force myself to remember we need to both solve the problem together. The client gave me a best guess. I delivered a first draft. We examine it together and improve it together. We get to employ all sort of happy and over-used words: collaboration and synergy.
The client left familiar territory the moment they started changes processes. Like coming to the edge of a map – y’know back when maps were on paper. You just don’t know what is beyond the edges. We must leave room for trials and improvements that come in small steps: “baby steps”. Client and developer ought to face the unknown together and not play gotcha.
I did the best I could with their colors. Stevie and I tried to improve the layout to incorporate their colors and fonts. Let’s share our screen with Bram. Together, we can get the colors right. Find good colors for buttons and bring the application closer to the brand book. We possess the expertise with HTML and CSS to make the changes. Bram’s opinion matters the most on what is right and wrong. We need to quit guessing and do it together.
One of my guidepost sayings, borrowed from a book called “7 Habits of Successful People”, is: “Begin with the ends in mind.” In the early days, we defined “end” as meeting the specifications described in writing in the requirements or design document. The “end” must be successful software. The “end” means an application the client loves, otherwise, why the heck are they paying us.
How does a team develop a comprehensive vision for the “end” of a multi-phased, multi-part, and likely multi-year investment? We must begin with the end in mind. How do we make sure that our beginning takes us all the way to the end. First, admit we will wiggle, make mistakes, diverge a bit, and may have to re-address issues. Second, we must keep the wiggles, mistakes, and re-work to a minimum. Rework and wiggles erode budgets and erode confidence.
My vision for the end of this project includes the following concepts:
• Efficient and flexible database architecture (achieved by following the database normalization forms developed by Dr. Codd in the early 1970s)
• Uniform, unified, consistent, comprehensive at all levels from user interface on the client’s browser to database operations on the server
• The application improves corporate efficiencies, meets operational goals
• And generates stunning, accurate, gorgeous invoices.
Everything, our software will do must result in improving the revenue stream. Walk with me on this exploration. Inspectors inspect client assets. In order for inspectors to inspect the assets the inspector must be qualified. The qualification process requires training and supervision and periodic reviews. The process of tracking inspectors, inspector’s qualifications, inspector’s training all enhance the invoicing process. Imagine the cost of performing an inspection by someone un-qualified. To carry this forward, the Government of Belgium occasionally audits Electrotest. All of the records related to inspectors training, reviews, and qualifications must be visible and organized for a successful audit. Fail that audit, watch revenue drop. We must have integrity from the invoice through to the inspections to the inspectors to their qualifications and training. The building blocks snap together.
I can start anywhere in the software and its numerous modules or tables then walk to the invoicing process. Let’s look at customer complains. We need to track those. Some complaints may result in credit memos and refunds to clients. Complaint tracking will provide a foundation for root-cause analysis. Complaints cost money. Understanding and diagnosing complaints may save money and increase revenue.
Start anywhere in our suite of software and the path leads to invoices. On this project, the client determined that our first major deliverable will be invoicing. With Electrotest, we begin with the end in mind. We got so committed to this concept, we delivered the end of the project first. Logically, not possible, but think about that.
We build the invoice module immediately, then everything there after must flow smoothly to that invoice modules. If it flows, its could be in scope. If it diverges, it is out of scope.
Without a large, comprehensive, cumbersome document our team shares one vision for a successful venture. We begin with the end in mind: gorgeous, accurate invoices that enhance revenue and reduce regulatory risks.
That is mission statement. That phrase becomes the long-term target for every action.
When pressured to develop a parallel project that does not honor the stated mission, we risk failure. Therefore, I asked how does this lifting module help generate gorgeous and accurate invoices? I am asking how does this module integrate with the long-term corporate goals. I am asking does the task, introduced in the third month, enhance our ability to solve long-term systemic problems, or not?
Our wall, our software architecture has a hole in it. We will fill that hole, creating solutions, and writing code. Do we shift our priorities to the problem? Or do we invest our time and funds on creating gorgeous invoices? The problem remains at our horizon. We all see it. We are all impatient to find solutions.
Does creating a stand-alone application for one of several divisions of a national company bring us closer to the end? Or not?
Nobody wants to hear “no”. I don’t mind saying it. So easy: “No!” We must finish invoices so that we understand the entire architecture and the gaps. We will then circle back together to address these gaps as a team.
The answer is: Absolutely we will build this module, but in time when we have consensus on the fundamentals.
When we said: “later” or “in due time”, I think people heard us saying: “We don’t have the time nor resources to solve the problem” I think people heard us saying: “No”. I am certain people heard me say: “We need to follow a linear progression” or some dreaded phrase.
So, we got offered more programmers. If you need software developed, hire programmers. If you can’t get code written fast enough, hire more programmers.
Risks of Adding Staff
It seems entirely contrary to state that adding programmers does not speed the development process. I think I first read that statement in a book called “Decline and Fall of the American Programmer”. Edward Yourdon wrote this book in 1992. One might think that if it takes five programmers a month to write a service order module, then it should take ten programmers two weeks to accomplish that same task.
Software development is a craft. We bang on tin and build stuff from raw materials, in a way. We build to a shared vision with shared tools in a shared environment. New team members must thoroughly embrace the team’s standards: table names and field names; coding techniques; tools and techniques employed. In the hundreds of tables, many have the field “display_order” which permits the users to adjust how they want data presented. Take “By Post” and “By Email” as examples. Alphabetically, “By Email” is first in English. But not across all languages. Maybe “By Email” is preferred and ought to be first. We include a field called “display_order” so that users can make that decision. In all tables, except two the field is named: display_order. In exactly two tables, that same field is called: order_nr (with number abbreviated). Suddenly, the team writes code that does not compile. We have to look it up. Those two misnamed fields are ticking timebombs for any developer who goes near them. It costs us time, money, and emotional frustration to have to look that up every time.
Adding staff means bringing new people to the team’s standards. Reinforcing the standards with reviews and feedback. It means more communication: does everyone have an assignment? Does everyone understand the requirements?
More programmers can slow a project down. One or more people adopt roles of trainer, coach, and supervisor. One or more people spend more time managing process instead of developing code.
Want to know what slows software projects down?
It takes time for both the client and the developers to study at challenging problems. We must stand shoulder-to-shoulder looking at the issue. Electrotest and the developers do not yet fully understand how to model the inspection data and pricing process with database tables. Can’t add a programmer to solve that problem.
We ought to add programmers when the lack of programmers is the problem. If there are hours in a week when the developers are idle, then adding a programmer may reduce the idle time, but will not move the project forward.
Adding programmers when the lack of programmers is not the core issue, then we get longer meetings. We talk more and do less. That’s non-intuitive. When we do add programmers, we must accept that for the first weeks and months, we lose efficiency while investing time in building the team. Nobody new knows the project, the tables, the structures, the objectives, the standards, the look-and-feel. A new person must learn all of that, no matter how experienced they are as developer. The newbie sits down with expertise saying: “Well at my last job, we did it this way.” “Well on my last project, I was taught to do it this way. It is better.” Team members are human beings. If we say: Shut up and listen, we fail at building a team. On the other hand, we can’t keep shifting standards, tables, techniques, tools, and process to accommodate each new member. This is how we play football on this team. You joined this team, please embrace that. We’re pretty good too.
That’s a tough balance. Finding it takes time and energy. People still get pissed off or feel they’ve been pissed on. Or ignored. Or under appreciated. Or unheard. Damn people are humans. When people join teams, they bring all of that humanity with them.
Let’s go build some invoices and an invoicing module. It must work in Dutch and French. It must accommodate crappy data from an external legacy system. This module will replace a workflow performed by Electrotest staff members using Microsoft Excel.
Until next time, be well and do good and have fun.