Book Summary: Programming Beyond Practices
Programming Beyond Practices is about the higher level thinking that software developers need to be successful. It’s not written like most programming books. It mostly uses second-person narrative so you are the main character in the book. It’s really a collection of short stories that describe many different problems faced by developers, as well as the thought process involved in discovering how to solve them. Reading the book almost feels like learning from a mentor. It’s interesting to read how an expert developer thinks through real-life issues. The book is about critical thinking, problem solving, and soft skills, rather than coding skills.
The following quote from the author sums up the purpose of the book:
“I wrote this book because I believe the shift away from ‘programmer as coding specialist’ is inevitable. If that’s true, then our entire field will need to prepare itself for the not-so-distant future where ‘programmer as skilled solver of ordinary human problems’ becomes the norm.”— Gregory T. Brown
In this book summary, I will summarize each chapter as well as describe key takeaways. The story arc of this book covers a full career in software development so you will observe the main character having more and more responsibility and mastery.
This chapter is about exploring new product ideas through rapid prototyping. You work for an agency that helps clients navigate the early stages of product design and project planning.
With a partner, you work on a functional prototype of a music video recommendations system for a client. To get the prototype done, you use principles such as “The Simplest Thing That Could Possibly Work” and the YAGNI principle. This stands for “You aren’t gonna need it” and is a design principle that says functionality should not be added until it’s truly necessary to do so.
After checking assumptions with the client and limiting the scope of the work, a functional prototype is complete by the end of the first day. In the second day, a visual representation of the data used behind-the-scenes is added to the page. This is only temporary and helps the client understand what the program is actually doing.
It is important to ask questions that reveal the goals of all the stakeholders in a project. This helps validate assumptions and help gives clarity to how other people see the problem.
Before worrying about style details, it is a good idea to use wireframes or rough sketches to show the basic structure of an application.
A live test system can be super helpful for collecting feedback from the client. This doesn’t have to be production-ready or use any code that will be in the final product. The important thing is to be able to demonstrate basic functionality and allow people to interact with the product.
This chapter is about the many issues that can crop up whenever a production codebase is gradually extended to fit a new purpose.
Your company has an official knowledge base for its product. You’ve been asked to build a public wiki to go along with it that shares the same codebase and infrastructure as the official knowledge base. The wiki you develop is similar to the old knowledge base system, except the wiki is editable by anyone.
After getting the wiki up, you add a sidebar that has lists of articles. This sidebar quickly causes an error. The lists in the sidebar show the last time articles were updated. Some old records had null values for their ‘last updated’ timestamps because they were created before you started tracking update times. This was a hidden dependencies at the data layer.
Part of the sidebar is a list of the five most popular articles. This data will need to be pulled from the site’s analytics service through an API call. But an API call on each page load seems wasteful and would introduce a hard dependency on an external service. You end up creating a script that runs every four hours and pulls the visitor count for each page into the application’s database. Since this code runs outside the main application, a failure won’t be that bad.
There is a problem after the wiki has been live a few months. <script> tags were allowed in the wiki articles and now some articles are being used for nefarious purposes. You reused some code from the knowledge base system on the wiki. The knowledge base was only updated by trusted administrators so allowing <script> tags was fine. The wiki can be updated by anybody. This was another hidden dependency issue.
Always be on the lookout for hidden dependencies even in small updates. Even when existing features are not modified, a change may still break things.
Shared resources from outside your own codebase form a “hidden dependency web” that can cause side effects and failures.
Besides using constraints and validations whenever possible, you should also have good monitoring in place so that unexpected system failures are quickly noticed.
When reusing code and tools, small changes in context can lead to big problems. Make sure to carefully think out any reuse.
This chapter is about the various ways that third-party systems can cause failures, as well as how flawed thinking about service integrations can lead to bad decision making.
You run an online educational journal for programmers. You are meeting with your friend for an annual retrospective to discuss some challenges with third-party software integrations.
One major problem had to do with a third-party newsletter service. You didn’t test that much before sending the initial email was because the service was so popular. You assumed a popular service would have no issues. However, your use case of including code in emails was not a common use case. You should always test for and have have a plan for services failing.
The next issue discussed is when the site’s log-in system stopped working all of a sudden. A popular authentication provider was used. The system failed because the application depended on a library that used an old version of the authentication provider API, which was eventually discontinued. As soon as the provider turned off that API, the login feature was completely broken.
No one considered the possibility that the popular API would be discontinued. Also, no careful thought was given to the differences between integrating with a third-party library and a third-party web service. The library will technically work forever but it is dependent upon the web service.
You eventually ran into another issue with the API. The mocks used in the libraries tests were not set up correctly to handle the API’s new schema.
You should never completely trust external services, especially if it is for something other than what it is well known for. It can be helpful to look for examples of others successfully using a service to solve similar problems. This will give you a clue if the service will be a good solution.
While a library can only break things if your codebase changes, an external service can break or change behavior anytime without notice.
When there is a change to a service dependency, make sure to check for old mock objects. You should have some of your tests run against the real services to help prevent misleading test results
You should be constantly evaluating service dependencies.
This chapter covers some straightforward tactics for breaking down and solving challenging problems in a methodical fashion.
You are mentoring a friend who is in the early stages of a career in software development. She is good at well-defined tasks but struggles on problems with lots of fine grained details. Coding puzzles can help explore general problem solving techniques so you give your friend a puzzle and help her think through how to solve it.
Here is a list of tips you give your friend as she is solving the puzzle:
The first thing to do is to gather the facts and state them plainly.
Next it can be helpful to work part of the problem by hand before writing code.
The puzzle has input data. It is important to validate the input data before attempting to process it.
Make use of deductive reasoning to check your work
Solve simple problems to understand more difficult ones
Make sense of a problem by writing your own notes, then pair things down until what’s left are the essential details.
Large problems should be broken into simple sub-problems that you already know how to solve. And those sub-problems may be able to be broken down further.
Before coding anything, work through some possible solutions on paper. This allows you to discover how things work together without being bogged down with implementation details.
You should always validate any data before processing it.
This chapter shows a step-by-step approach to bottom-up software design, and examines the tradeoffs of this way of working.
You are helping to teach a software design course. To demonstrate where design decisions come from, you are going to lead a class discussion on the process to build a minimal simulation of a just-in-time production workflow. This simulation will include the main steps from a products creations to a customer purchasing the product. In a just-in-time workflow, the customer purchase is what causes everything to happen going back to a new product being created.
You start by having the class identify the nouns and verbs. Words like widget, crate, supplier, order, and produce. Then ask students to put two words together into a sentence. A student suggests to “build a crate and put a widget in it”. So that is where implementation starts.
Beginning with a minimal slice of functionality is helpful when considering a large project. Through discussion, students realize the challenge of designing a system from the bottom up is untangling the connections between objects so that they can be implemented in small slices rather than big chunks.
After implementing some of the relationships, you show the importance of avoiding unnecessary temporal coupling between objects. You also gradually extract reusable parts and protocols. After all the basic components of the system are developed, you experiment by putting them together in new ways.
When using a bottom-up design, start by listing nouns and verbs relating to the problem you are solving. Then create a short meaningful sentence from the words. This sentence can help you determine the first feature to implement.
As your project gets bigger, pay attention to the connections between objects. Think about how to create designs that are more flexible so artificial constraints are not imposed.
If you are going to extract reusable objects and functions, these should be things that will probably not change much over time.
This chapter focuses on how small adjustments to the basic building blocks of a data model can fundamentally change how people interact with a system for the better.
You work for a small business that wants to replace a decade-old time tracking application. You are working with the original application developer and are sharing your plan for building a better system.
Your first suggestion is to decouple conceptual modeling from the physical modeling. Instead of modeling work sessions as intervals, you suggest to record punches as individual events and convert the puch data to intervals at the application level.
You also design an explicit model for tracking data changes. Instead of one data model for everything, you suggest using three: punches, puch adjustments, and timesheet revisions. Making changes and understanding previous changes will be easier. This is all about “reducing incidental complexity as much as possible by minimizing mutable state.”
Workflow design and data modeling go hand in hand. You discuss proposed staff workflow to interact with this new data model.
It is better to store data in its raw form instead of trying to get the data also communicate specific concepts. Using this chapter’s example, just store time punches and don’t worry about storing information about the interval. You can process raw data later but it can be challenging to get the correct information from complex models if your requirements change.
Not many projects just involve basic CRUD operations on single records. It is important to think about how data will be presented, queried, and modified over time.
When developing a data model, make sure to think about how to simply communicate the various operations on the data. Users should be able to easily preview, annotate, approve, audit, and revert data changes.
When developing data workflows for an organization, they must work within the current structure and culture of the organization. As Melvin Conway said, “Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.”
This chapter is about some common anti-patterns that lead to struggles in software project management, and how incremental process improvements at all levels can alleviate some of those pains.
You are a consultant that is helping a small product company through some growing pains. TagSail is a web application to help people find nearby yard sales. In your first day on-site, there is a minor emergency.
A reverse geocoding API is failing causing all home page request to show a server error. You suggest to temporarily disable the API since it is not core to the sites functionality but an engineer thinks he can fix the problem quickly since he had previously created a proof-of-concept of his proposed fix. Since the proposed fix had never been tested in a realistic environment, you still think it’s better to start with temporarily disabling the API.
The next issue dealt with is that developers are spending half their time working on integrations with other classified networks. These integrations are a service to their customers but don’t bring in additional revenue. After looking at some statistics that show a diminishing rate or return, you suggest limiting how much time developers spend on integrations. This time budgeting helps to limit the impact of high-risk areas on a project.
A few weeks later you visit the company again and discover that reviewing and approving work has become a bottleneck. Developers keep getting more work to keep them busy while they are waiting for various blockers on their tasks. More and more code is being created but hardly anything is being shipped. You get the company to change some processes to solve this issue.
If your entire site is down, disable features to get the site back up as soon as possible. Then repair work can be done without as much pressure.
Use data and calculations to determine areas where you are overcommitted. Set time constraints on how much you work on certain non-essential areas.
It is more important to make sure code gets shipped regularly than make sure everyone is staying busy. Unshipped code is ‘perishable inventory with a cost of carry’.
This final chapter has a slightly different format. It contains a futuristic scenario about what programming might be like if we could focus purely on problem solving and communication rather than writing code.
You are preparing a report for a city to help set the budget for sidewalk repairs with the help of your virtual assistant, Robo.
Robo helps a lot in researching and visualizing data. Robo’s research features are like an advanced search engine combined with an organized knowledge base. Humans still need to do the thinking, synthesizing, and creating.
Robo makes things easy and seamless that would normally take a lot of work and trying various libraries and data together. Some operations Robo helps with are matching a geographic coordinate to a region that contains it, exporting data tables from web services, and generating a cumulative flow graph from a table.
The book’s author envisions a time when there will be more and more software abstractions. The ‘programmers’ of the future will be less focused on “rough, low-level, tedious tools” and more focus on the problems themselves and a human-centric outlook.
-Beau Carnes, software developer