Assumed Audience: Hackers, programmers, users, and anyone that cares about computing freedom. Discuss on Hacker News, but please don’t post on lobste.rs because I do not have an account.
Epistemic Status: Extremely confident, enough to bet my career on it.
This post borrows heavily from “Uncle” Bob Martin’s “The Future of Programming” and Voxxed CERN 2019 Keynote. It even has some of the same things.
I did this because those talks are excellent, and I largely agree with his points.
Watch the talks. If you can only watch one, watch the keynote.
Introduction
Like the last post, this post was started because of something I found one the Internet.
In short, a brand-new laptop had its CPU saturated with bloatware/spyware installed by the manufacturer, and once the software was uninstalled, the laptop worked as intended.
Well, I say “as intended,” but perhaps the saturated CPU was what was actually intended?
No matter what it was; that sort of thing only happened because the programmers that made the junkware were not professional.
They should have been. We all should be. It is essential because our society depends on us becoming professional now.
What Does Professionalism Mean?
Before we can talk about professionalism in the industry, we have to define it.
We could start with the Software Engineering Code of Ethics and Professional Practice, but this code of ethics is “sophomoric” (see also this post which is probably by the same author).
Why is it sophomoric? Because it puts the public interest above that of the user. Taken at face value, this could mean that the public should decide how we, as individuals, use our computers.
I don’t know about you, but I don’t want the public deciding that I can’t build my computer how I want and that I must run Windows and that I can’t run Linux on it. Or FreeBSD.
I should probably switch to FreeBSD eventually. I have code in FreeBSD, and I run ZFS.
We could start with the ACM Code of Ethics and Professional Conduct, but it makes the same mistake of putting the public interest above that of the user or users.
No thanks.
Instead, I want a code of ethics that makes working for and obeying the user first priority!
That’s the same definition of professional that doctors use (“do no harm” even when society has an interest in the death of the patient, such as a murderer), as well as lawyers (who must defend clients they know are guilty).
So why can’t we have the same definition?
This definition does not preclude projects where the interest of society are first and foremost. Those projects will be critical infrastructure where all of society are the users.
I think that something that hits all of the points in this list should work:
- Making the software work for the user, not for society or even the developer of the software, much like a lawyer works for his client regardless because doing otherwise can be disastrous.
- Making the software work with the user by making the user experience as good as possible.
- Making the software lean so that it uses as little of the user’s resources as possible.
- Collecting as little data as possible. This includes not surveilling the user.
- Securing data that does have to be collected.
- Refusing to sell data to third parties.
- Refusing to profit from data.
- Refusing to use dark patterns.
- Refusing to give up data to governments without proper warrants.
- Providing source code to users to prevent vendor lock-in or other anti-competitive behavior.
- When possible, making the software “fit for purpose” by proving it fulfills its specification.
- When it exists, providing the proof to users.
- Accepting liability for our software.
- Conducting public and independent inquiries into the cause of accidents, like the aviation and engineering industries.
- Refusing to work on blockchains/cryptocurrencies, ads, and other unethical projects.
- Creating software that produces value, instead of extracting it.
- Respecting the intellectual property of the code we use, including knowing the provenance of that code.
In essence, this list makes it clear that users own their computers, and their machines are there to serve them, not anyone else.
Duty to Users
So the first and foremost thing is that we should recognize that we have a duty to users.
But why do we have a duty to users? Why should we make machines serve them?
“We Rule the World”
We have a duty because
We rule the world.
The world doesn’t know this yet. We don’t quite know it yet.
Other people believe that they rule the world, but they write the rules down, and they hand them to us. And then we write the rules that go into the machines that execute everything that happens on this planet nowadays.
No law can be enacted without software; no law can be enforced without software. No government can act without software.
We rule the world.
If we rule the world, then that means people depend on us to make the world work for them because
With great power comes great responsibility.
It is our responsibility.
“It Was Us”
Unfortunately, we have not used that power responsibly. In fact, we’ve done the exact opposite; we have made things worse for so many people.
This next portion will be taken and edited from one of my previous posts.
Volkswagen
Most people who pay attention are aware of the Volkswagen emissions scandal.
Uncle Bob Martin said,
…The CEO of Volkswagen North America…blamed a couple of software developers for cheating the EPA….But it was software developers that wrote that code. It was us, who wrote that code to cheat. Some programmers wrote cheating code.
Do you think they knew? “If in test mode…” I think they probally knew!
The programmers wrote the code anyway.
It was us.
Toyota
How many people have been killed by cars, by software in cars that have gone bad? Dozens. Dozens.
There have been a number of people where the cars just run completely out of control. The brake won’t work; the accelerator’s down on the floor. The car accelerates out of control and smashes into something.
There have been several dozen of those, and there was actually a big suit not too long ago where Toyota had to pay out an awful lot of money because people were being killed, and more were being injured because of software in cars.
The expert testimony in that case was given by Michael Barr, and it is terrifying. The transcript is here, and the slides are here.
Did programmers know that the code they were writing was shoddy? Did they know that they were ignoring many important best practices? I think they knew.
The programmers wrote the code anyway.
It was us.
John Deere
farms.com published an article arguing that farmers deserve the right to repair their tractors, and they are right.
The gist: tractor companies are withholding software tools from farmers in order to force them to take their tractors to licensed dealerships.
Some programmer(s) somewhere wrote those software tools. They did it, probably knowing that farmers would never get access to those tools and that their employer would use that assymetry to dishonestly extract payments from farmers by removing their right to repair.
Here is a rebuttal from John Deere.
The programmers wrote the code anyway.
It was us.
Apple
Like tractor manufacturers, Apple likes to think that they still own the device they sold to you.
They made it so that you cannot install any apps besides from their officially-curated, and broken, App Store.
Oh, and they refuse you the right to repair too.
Apple’s programmers probably know that Apple does this to customers.
The programmers wrote the code anyway.
It was us.
Google sells data from users. Google makes users jump through hoops to get their own data.
Did the programmers know what they were writing? I think they know.
The programmers wrote the code anyway.
It was us.
Besides selling data like Google, Facebook manipulated users (and probably still does).
Did you think the programmers knew that what they were doing would manipulate users? I think they knew.
The programmers wrote the code anyway.
It was us.
Lenovo
Lenovo put junkware on laptops that makes them unusable. That junkware would not exist if programmers didn’t write it.
Did the programmers know that the code would make laptops unusable? I think they knew.
The programmers wrote the code anyway.
It was us.
We Made This World
If programmers had refused to write any of the code above, then at least that part of the problem would not exist.
Drew DeVault wrote that “we are complicit in our employer’s deeds.” He is right.
Because we did this. We did this to ourselves. We did this to others.
We wrote the “algorithms” that manipulate people into believing lies. We wrote the recommendation engines that radicalize. We wrote the software that hurt people. We wrote the software that destroyed livelihoods.
We wrote the software.
We are killing people.
We did not get into this business to kill people, but we are killing people.
Even worse, we did this to our loved ones.
We made the world that our loved ones must now suffer through.
Thing of that: the world is a worse place for your loved ones because of you. And me. And all of us.
It was us.
The Coming Catastrophe
So we have no one but ourselves to blame for what’s coming.
And one day, one of us is going to do something dumb, and maybe not even that dumb, and the result will be a catastrophe where tens of thousands of people die.
And you can imagine what it would be. There’s a whole bunch of possibilities there. Plane crashes into a football stadium.
And when this happens (and it will happen, it has to happen; it’s just a matter of time), when this happens, the politicians of the world will rise up, as they should, in righteous indignation, which they should have, and they will point their fingers right at us, and they will ask us the question, “How could you have let this happen?”
We Can’t Hide
Many programmers may think that they may be able to hide behind their managers when their managers tell them to do something they know is wrong.
Absolutely not.
…The CEO of Volkswagen North America…blamed a couple of software developers for cheating the EPA.
“Oh, yeah, it was a couple of software developers that did it for whatever reason.”
What a great line: “whatever reason.”
Now the weird part about that is that it was a couple of software developers, maybe more than two. But it was software developers who wrote that code.
Those software developers, according to Uncle Bob Martin, received their just desserts:
…so those guys, whoever they were, they put their fingers on the keyboard and typed the cheating code.
They’re in jail, and they deserve to be in jail.
You and I could go to jail for the code we write.
We should not be spared, so we shall not be spared.
And when [a catastrophe] happens (and it will happen, it has to happen; it’s just a matter of time), when this happens, the politicians of the world will rise up, as they should, in righteous indignation, which they should have, and they will point their fingers right at us, and they will ask us the question, “How could you have let this happen?”
They won’t point at our managers because our managers will say, “Oh it was some software guys who did it for whatever reason.”
They will point at us, and they will ask us this question, and we’d better have an answer for them because if our answer is, “Well, my boss made me do that,” that is not going to fly!
Uncle Bob says that when the catastophe happens, our industry will be regulated, and that we want to be ready for that.
I agree, for the most part. I just don’t think Uncle Bob is pessimistic enough. He says the catastrophe will cost tens of thousands of lives, and I think that’s optimistic.
I think two worse things could happen: we could lose our freedom to use our computers as we wish, or even worse, society as we know it could be destroyed.
Losing Computing Freedom
Think I’m exaggerating?
Companies have resources. Hobbyist programmers making Free and Open Source Software (FOSS) don’t have resources.
Sometimes they do, but they are provided by companies, so companies still hold the leverage.
Companies can use those resources to make software flashier, more attractive, more appealing, and easier to use. They can also hide the cost of the software temporarily by using it as a loss leader, or just while they build a userbase.
Without resources, FOSS just can’t compete on user experience.
This is why we (FOSS authors) need to attract and recruit UX designers to our cause and to focus on UX in FOSS.
We need better UX than closed alternatives to preserve computing freedom. If we don’t, non-technical users will gradually drift to closed alternatives until we have lost.
On top of that, software has a danger of vendor lock-in and network effects, so companies can restrict users from moving to alternatives, even if users want to move to FOSS alternatives.
This means that companies exert control over software in a way that few companies in other industries can.
It gets worse.
Companies have incentives to lock down our machines with DRM or “Trusted” Platform Modules that only accept keys from themselves or remote attestation to ensure you can only run “acceptable” software.
They will use that power. They are using that power.
To say nothing of Google and Apple preventing developers from publishing on their platforms for dumb reasons.
FOSS cannot compete with this.
The most terrifying thing is that if this capability exists, governments will use it. It’s just too much power to leave untapped for the power-hungry petty tyrants.
Destroying Society
But it gets worse.
I was not exaggerating when I said that society could be destroyed as we know it.
I mentioned before that recommendation engines radicalize. I’m sure you can imagine many society-ending things that can happen because of radicalized people.
But I also just mentioned how governments may use the tools that exist to control the populace. There might not be an explicit conspiracy about destroying our representative republics and democracies, but locking the populace down under surveillance would have the same effect through social cooling. And it could eventually lead to direct tyranny.
After all, many governments tried (and both succeeded and failed) to lock us in our homes or require a medical intervention for a virus that mostly kills people past life expectancy. One government also implemented controls on a person’s ability to operate in daily life with bank account freezes for donating to a protest.
I would consider a society destroyed as we know it if it was a republic that descended into tyranny.
But I could also imagine that Big Tech continues its rent-seeking, parasitic behavior in this late-stage capitalism until most money spent is in useless ads as companies desperately seek to maintain their positions; if that happens, the economy could run dry as fewer useful things are made, which could lead to collapse.
And I could definitely see it leading to a collapse large enough to undo the political bonds we have, especially where people seem to be at each other’s throats in general.
I highly doubt our current societies can bypass the Great Filter.
In other words, I think that the software industry is hurtling us toward collision with a turning point, and I believe our society will fundamentally change afterward, even if it survives.
What Should We Do?
So what should we do?
After all, the companies have the power, right? What can we do, as programmers?
Plenty.
Without us, the companies would not have their software. Without us, they have no product. Without us, they are just a canvas bag full of bugs.
Just Say No
So just tell your boss no when he tells you to do something wrong.
“Oh, but Gavin, if I were to do that, I would be fired and would struggle to find a job because all companies are like.”
Yes, there is some truth to that. Otherwise, professionalizing our profession wouldn’t be necessary because in order to be able to say no, we need to get rid of managers.
Get Rid of Managers
Yes, you read that right: we need to get rid of managers.
I learned this principle from a book by Uncle Bob Martin, funnily enough. However, it was from the Foreword of Clean Coder written by Matthew Heusser.
In the Foreword, he tells a story about “Batman and Robin,” his term for himself and a coworker he called Joe.
They were the project managers for a time critical project, a project that had been launched when the government opened a new product. They had to hit their deadlines or they’d “be out of business.”
In fact, Matt said, “It was the sort of environment in which some people complain, and others point out that ‘pressure makes diamonds.’” Joe happened to be one of the people that were fond of that little saying.
So they pushed their developers hard; they removed obstacles, made sure people had what they needed, cajoled them when they were obstinate, and basically micromanaged them.
And you know what? They managed to get it done on time.
On launch day, the deadline they had to hit or they would be out of business, they were ready to flip the switch when Joe walked in and said that the legal department didn’t have the enrollment forms ready.
Matt was ready. He said, “All right partner, let’s do this one more time.”
Joe asked, “What do you mean, Matt?”
“You know,” Matt replied. “Our usual song and dance….Let’s go hang out in their cubicles, give them the evil eye, and get this thing done!”
Joe did not agree. “Matt, these are professionals. We can’t just stare them down and insist they sacrifice their personal lives for our little project.”
Matt was confused. “…Joe…what do you think we’ve been doing to the engineering team for the past four months?”
“Yes, but these are professionals.”
After a lot of thought, Matt posed this question: “Shouldn’t we have been able to ask the staff when they would be done, get a firm answer, believe the answer we were given, and not be burned by that belief?
“Certainly, for professionals, we should…and, at the same time, we could not…”
“Joe didn’t trust our answer, and felt comfortable micromanaging the tech team — and at the same time, for some reason, he did trust the legal team and was not willing to micromanage them.
“Somehow, the legal team had demonstrated professionalism in a way the technical team had not.”
If we act like professionals, we will be treated like professionals.
This means we won’t need managers anymore.
Funny and enlightening tangent, I retold this same story at the 2016 OpenWest Conference as part of a presentation about why we need to be professional programmers.
There was a manager in the audience who immediately tweeted that I had no idea what managers do.
Since that time, I have been in the workplace and found out that I was exactly correct; that manager was just not happy that I was saying we should do without people like her.
This is how it works with professional engineering. Sure, there might be a project manager, but he certainly does not tell the head engineer what to do. Or if he does, and the engineer refuses, the engineer wins.
If we are professional, managers may exist, and they may tell us what to do, but we can say no. And true professionals will say no, especially when something goes against their professional Code of Ethics.
Remove Software Cancers
This may seem like a small thing, to be able to say no, but it means everything.
If we can say no, then when any company decides to add ads, programmers can say no. Software remains free of ad cancer.
If we can say no, then when any company decides to add surveillance to sell user data, programmers can say no. Software remains free of surveillance cancer.
If we can say no, then when any company decides to add censorship, programmers can say no. Software remains free of censorship cancer.
Without managers and the with the power to say no, we can’t be pressured into things that are bad for users.
Technical Debt
Just as importantly, if we can say no, then when any company decides to continue adding features when technical debt is too high, programmers can say no. Software remains free of technical debt cancer.
This last one will actually help companies too; they just don’t know it.
I’ve made the case elsewhere that getting rid of technical debt would actually save money long-term. The reason for this is that technical debt grows exponentially.
People forget what exponentially means and just how fast it grows.
Uncle Bob Martin makes this case too:
I expect stable productivity.
What does that mean? It means I do not expect you to be going slower and slower and slower with time.
How many of you have done that one? You’re working on a system, and the first few weeks of the system, we’ll say it’s a greenfield system…How fast can you go? The first few days, how fast can you go?
Some guy says, “Can you give me a feature?”
Hm…yes.
<engine_noise>
You start coding and code pours out of every orifice of your body.<engine_noise>
And it works! It works two weeks later! And business is looking at it going, “Wow! We’ve never seen programmers go that fast….You come back to that team a year later. “Can you give us a feature?”
“Whoo, probably takes like, six months.”
“But you used to be able to do this in two weeks.”
“Yeah, but you don’t know how complicated this system has become. Why, if we touch even one line of code, all *** could break loose! It’ll take us at least six months.”
…
I expect inexpensive adaptibility.
It should not be expensive to change software.
Want to prevent that from happening? You need the power to say no to new features until messes (technical debt) are cleaned up.
Becoming Professional
So what’s the catch?
Well, I’ve already said it: we need to act like professionals.
Discipline
The number one thing real professionals have that we don’t is discipline.
So that’s the first step to becoming professional: we need to gain discipline.
This will mean many things.
It will mean the discipline to clean up any technical debt before starting on new features.
It will mean the discipline to keep a high-coverage test suite updated so that you can refactor, press a button, and have confidence that the refactor was safe.
It will mean the discipline to not fear the code, to accept the personal risk to change it, to dominate the software and never let it dominate you.
Uncle Bob continues:
Everything you [currently] do as a programmer is out of fear, and this is wildly irresponsible. This is incredibly irresponsible. No profession could tolerate that. You must not lose control of the thing you have created.
And that’s typically what we do: we lose control of our own creation. And so our own creation dominates us. We don’t dominate it.
Liability
With good discipline should come a willingness to take on liability for our work. This includes vetting dependencies that we use, which should be pinned to specific versions.
This liability should have limits; for example, it should only kick in when we don’t do due diligence (and we need an objective way to measure due diligence), and it should only apply when users use our software in the way we documented.
But it should exist, and we should take it seriously. We should hold each other accountable for it, preferably through a professional organization.
Certification
That professional organization should have standards, and it should have a certification process to ensure that every professional programmer meets those standards.
However, such a certification should not preclude non-certified programmers from working; it should be permissible for them to work under the supervision of a certified professional programmer.
Ethics
Of course, such a professional organization should have a standard of ethics, a Code of Ethics, and it must include making software obey users, not employers, creators, or society.
I’m willing to hash out things and compromise on many details, just as long as there is some sort of agreed on standard.
But I will not compromise on making software obey users because otherwise, the coming catastrophe(s) will still happen.
This Code of Ethics must also include properly respecting and using intellectual property, including knowing the true provenance of any code used internally or as dependencies.
Yes, this means that every use of something like GitHub’s Copilot would have to be carefully monitored and the result vetted. But that’s a good thing.
I Will Call You Out
Up to now, I’ve been relatively benign. That ends today.
I run the gimick Twitter account @software_crimes
, called Programmers
Against Humanity.
This account was inspired by the now non-existent Architects Against Humanity
(@arch_crimes
) account.
I originally started this account to highlight bugs that people run into and how programmers cause pain for the rest of humanity.
But starting today, I’m going to expand my equal opportunity criticism to anything that harms humanity that could have been prevented by a programmer saying no.
So if you are a programmer, beware. I will call you out for unprofessional behavior. By name.
If I will call myself out, don’t expect to be safe.
But should you be one of the good ones, or wish to have someone shame your employer, contact me.
That address is an encrypted email address, and the public key is on that contact page.
Please use the encryption, especially if you need to keep the information confidential.
Be aware that subject lines may not be encrypted. You can use a safe subject line if necessary; I won’t mind. Keep yourself safe first.
Let’s start building good peer pressure in the industry.
Professional Programmer Standards
Before I bring this post to a close, I realized that I have a lot of complaints in this post, and while I presented a solution, I didn’t go far enough. I didn’t present anything concrete, and we need something concrete because
If you’re going to write code that sits on the network, if you’re going to write code that operates in people’s homes, if you’re going to write code that transports people or deals with money or deals with policy or deals with anything that society depends upon, you’re likely to find yourself regulated sometime in the next N years.
And when that happens, I would like to get there first because governments are inherently lazy. If we come up with the rules first, then when society decides to regulate us, we can simply hand them the rules. And they’ll say, “Oh, great! We’ll use those.”
I wanted a code of ethics now, and I wanted a standard of care now. I want to be ready; I should write them myself.
So I gave it a shot.
These are first drafts, and I am willing to improve them.
The Professional Programmer’s Code of Ethics
I am a professional programmer.
I have a duty the users of my software, and my mission is to fulfill that duty with no compromises.
I will build my software to work for users. I will make my software a faithful agent that obeys users perfectly and without hesitation.
I will only think of users’ interests and remain free of other influences or loyalties, including other portions of this Code of Ethics should they impinge on users’ interests.
I will build my software to work with users, to minimize confusion, mistakes, and wasted effort. I will build my software to never use dark patterns or otherwise manipulate users.
I will build my software to provide value for users. I will accept users’ value judgments over my own.
I will make my software use as few resources as possible.
I will never build my software to surveil users. I will build my software to collect as little data as possible and to only use that data in furthering users’ interests.
I will build my software to be as secure as possible. I will build my software to never lose or leak the data users have entrusted to it, and I will never sell that data. I will never give that data away unless legally compelled.
I will provide users with all source code and documentation for my software, including documentation that will allow them to build and change the software themselves, and I will not prevent them from doing so.
I will ensure that my software is fit for purpose. I will provide the tools I use to ensure that fitness to users so that they can verify it.
I will build my software to the Professional Programmer’s Standard of Care, including any items specific to the software. If I provide my professional stamp of approval on my software for certain use to any user and if that user should correctly use my software and suffer damages from any deficiency in my software caused by my failure to adhere to the Standard of Care, I will accept liability and compensate that user.
I will conduct or assist inquiries into the cause of any deficiency, including process deficiencies. Should any cause not be covered by the Professional Programmer’s Standard of Care, I will develop new processes to prevent similar deficiencies, and I will adopt those processes as part of the Standard of Care for my software and document them.
I will respect the intellectual property of others and have knowledge of the provenance of any intellectual property my software uses. I will accept liability if I fail to do so.
I will deliver my software to users as quickly and economically as possible.
Analysis
I’m not going to analyze the entire thing; instead, I’ll just mention a few things that might be either confusing or controversial.
I will build my software to work for users. I will make my software a faithful agent that obeys users perfectly and without hesitation.
This line is perhaps the most important in the entire Code. It is meant to enshrine the fact that software should serve only users.
It is also meant to enshrine Freedom 0 of the Four Essential Freedoms.
I will only think of users’ interests and remain free of other influences or loyalties, including other portions of this Code of Ethics should they impinge on users’ interests.
The first half of this sentence is probably self-explanatory, but why would I put in a possible loophole to the rest of the Code?
The reason is that sometimes, there will be conflicts between respecting the interests of users and following the rest of the Code. When that happens, something has to take precedence, and I think that users’ interests should always take precedence.
I will build my software to provide value for users. I will accept users’ value judgments over my own.
This line is what will prevent ads, scams, and other rent-seeking behavior from programmers.
I know that this line will be up to interpretation. That’s why I put the second sentence in: if there is a question, the user always decides what is valuable to them.
I will build my software to be as secure as possible. I will build my software to never lose or leak the data users have entrusted to it, and I will never sell that data. I will never give that data away unless legally compelled.
I separated selling of data from giving it away because I don’t think professional programmers should ever allow their employers to profit off of user data.
I will provide users with all source code and documentation for my software, including documentation that will allow them to build and change the software themselves, and I will not prevent them from doing so.
This line is meant to enshrine Freedom 1 of the Four Essential Freedoms, which will also prevent professional programmers from using vendor lock-in or other nasty tricks like remote attestation or DRM.
I will build my software to the Professional Programmer’s Standard of Care, including any items specific to the software. If I provide my professional stamp of approval on my software for certain use to any user and if that user should correctly use my software and suffer damages from any deficiency in my software caused by my failure to adhere to the Standard of Care, I will accept liability and compensate that user.
This is probably the paragraph that will rile people up the most. But this is also the paragraph I wrote most carefully.
The first sentence is self-explanatory; if there wasn’t any standard of care, there would be no real way to judge a professional programmer or hold him accountable.
The second sentence is about accepting that accountability. Yes, professionals accept accountability; that’s part of what it means to be a professional.
However, I know that people will still be riled up; after all, this paragraph might seem to prohibit professionals from working on FOSS in their spare time. It might seem to require them to accept liability for that software.
But it does nothing of the sort.
I carefully worded it so that the liability toward a user only kicks in if the professional programmer provides a stamp of approval to an individual user.
A professional programmer who works on FOSS does not provide such a stamp of approval. In fact, the license usually explicitly denies such a stamp of approval by saying the software is distributed “WITHOUT WARRANTY” of any kind.
This is fine.
The other reason people might get angry is because this paragraph seems to require accepting liability without pay. This is wrong because a professional programmer does not have to give a user a stamp of approval unless the user pays for it.
Of course, professional programmers could give stamps of approval without pay, but personally, I wouldn’t.
The last thing I want to say about this is regarding the phrases “for certain use” and “correctly use.”
It is my personal belief that a professional programmer should never give a stamp of approval to a user without knowing what the user will be using that software for. There are a few reasons for this:
- The user could have wrong ideas about what the software is good for.
- The user’s purposes could exceed the software’s limits.
- The user intends to misuse the software.
So as part of the process of accepting liability, the professional programmer should have the option of denying that option to any user that would refuse to use the software as intended, and the professional programmer should never have to provide a stamp of approval nor accept liability toward any user that misuses the software according to the uses for which the stamp was given, even if harm was caused by a defect in the software!
I will conduct or assist inquiries into the cause of any deficiency, including process deficiencies. Should any cause not be covered by the Professional Programmer’s Standard of Care, I will develop new processes to prevent similar deficiencies, and I will adopt those processes as part of the Standard of Care for my software and document them.
Every piece of software is different, with different requirements. The last part of this paragraph allows for that flexibility.
I will respect the intellectual property of others and have knowledge of the provenance of any intellectual property my software uses. I will accept liability if I fail to do so.
Looking at you, Microsoft and GitHub.
I will deliver my software to users as quickly and economically as possible.
Just because professionals are careful doesn’t mean they are excessive; time and money are resources too, and we should require as little of them as possible.
This is one reason why getting rid of technical debt is so important.
Questions
Now that I have analyzed my Code of Ethics as I have written it, I want to answer a possible question: why did I not enshrine Freedom 2 and Freedom 3 of the Four Essential Freedoms?
The reason for this is because those two freedoms are about distribution, and I believe there are some cases where end users distributing copies, whether they changed it or not, is not ideal.
However, users should always have the option of changing the software for themselves and running it as they see fit. Hence, I included phrases that protect those two freedoms for users.
The Professional Programmer’s Standard of Care
This is long, so you might want to skip straight to the analysis.
As a professional programmer, I will ensure that my software development process covers, in any order (except that Version Control and Project Management always comes first), at least the applicable portions of the following:
- Planning: To guide the planning process for C and Yao software.
- Version Control and Project Management: To ensure the project is under
version control and project management from the beginning.
- Version Control Requirements:
- Decide if branches may be required.
- Decide if tags may be required.
- Decide if integrated releases may be required.
- Decide if a central server may be required or if the VCS can be distributed.
- Figure out if any other features are required.
- Decide what platforms developers may use.
- Project Management Requirements:
- Decide the project management scheme.
- Decide if an issue tracker is needed.
- Decide how code reviews will be done (pull requests or manual, stacked or not, for example).
- Decide if CI/CD will be used.
- Decide if there will be a separate testing environment.
- Figure out if any other features are required.
- Version Control and Project Management Software:
- Find version control and project management software that meets the requirements decided above.
- Ensure that developers can install and use the software on the allowed platforms.
- Initialize a repository in the version control system.
- Ensure that all documentation, including design and requirements, is in the version control system.
- Initialize a project in the project management system.
- Add items in the project management system for the applicable portions of the rest of the checklist.
- Version Control Requirements:
- Software Requirements: To ensure that the requirements list is clear,
is unambiguous, has no conflicts, and has no outstanding questions.
- User Experience Requirements:
- Decide on an acceptable level of mistakes for the software to cause (what percentage of actions is allowed to be mistakes).
- List all “tasks” that the software might be used to do.
- For each task, come up with the workflow for that task.
- For each task’s workflow, identify possible areas of confusion (places where mistakes would be more likely to happen than the decided level of mistakes decided before) and fix them.
- For each task’s workflow, simplify it as much as possible.
- Clear up any ambiguity.
- Identify and resolve conflicts.
- Repeat this subprocess as many times as necessary until all workflows are as simple as possible and as clear as possible.
- User Interface Requirements:
- Identify every possible item in the user interface required to implement the user experience design.
- For each UI item, identify and document the accessibility requirements.
- For each UI item, identify and document the internationalization requirements.
- For each UI item, identify and document the localization requirements.
- Functional Requirements:
- List everything the software should do.
- Clear up any ambiguity.
- Identify and resolve conflicts.
- Algorithmic Requirments:
- Identify every possible portion of the software that may run an algorithm.
- Decide on time algorithmic requirements. The requirements may change with the size of the problem. If so, list both or all requirements and the size(s) where they should be split.
- Decide on space algorithmic requirements. The requirements may change with the size of the problem. If so, list both or all requirements and the size(s) where they should be split.
- Identify and resolve conflicts between time and space algorithmic requirements.
- Identify and resolve conflicts between algorithmic requirements and functional requirements.
- Performance Requirements:
- Identify the expected scale (possible size of the problem) that the software must reach.
- Identify the time resources that the software will be allocated.
- Using the expected scale and the time resources, calculate the needed performance requirements for each type of time resource.
- Identify the space resources that the software will be allocated.
- Using the expected scale and the space resources, calculate the needed performance requirements for each type of space resource.
- Identify and resolve conflicts between time and space performance requirements.
- Identify and resolve conflicts between performance requirements, algorithmic requirements, and functional requirements.
- Security Requirements:
- Create the threat model.
- Clear up any ambiguity in the model.
- Identify all types of attacks in the threat model.
- Clear up any ambiguity in the types of attacks.
- For each type of attack in the threat model, come up with a security requirement to mitigate it, or if necessary, eliminate it.
- Clear up any ambiguity in the security requirements.
- Identify and resolve conflicts between security requirements.
- Identify and resolve conflicts between security requirements and all previous types of requirements.
- Safety Requirements:
- List everything the software must not do. (Cannot crash, cannot drop messages, etc.)
- Clear up any ambiguity.
- Identify and resolve conflicts between safety requirements.
- Identify and resolve conflicts between safety requirements and all other types of requirements.
- Final Checks:
- Clear up any remaining ambiguity.
- Identify and resolve any remaining conflicts between requirements.
- Identify and resolve any remaining questions.
- Check all requirements into the software repository.
- User Experience Requirements:
- Resource Estimate: To estimate the amount of time and resources it
will take to accomplish the project.
- Identify all programmers who will be working on the project.
- Gather time estimates from all programmers.
- Use the estimates to make your best estimate of the time required for the project.
- Version Control and Project Management: To ensure the project is under
version control and project management from the beginning.
- Design: To design the software (or changes to the software), create or
obtain the specifications, and design the test tools and methods.
- Design: To design the software (or changes to the software).
- Review the requirements.
- Produce a design to meet the requirements, iterating down and up until the design is stable.
- Document all tasks that the software will be used to accomplish.
- Document all workflows for accomplishing the tasks.
- Document all state machines.
- Document all logic in pseudocode.
- Design Review: To ensure the completeness and correctness and
correctness of the design.
- Preparation:
- Examine the software and checklist and decide on a review strategy.
- Identify all tasks in the UX design.
- Identify all workflows in the UX design.
- Identify all user interface elements in the design.
- Identify all state machines in the design.
- Identify all internal loops in the design.
- Identify all variable limits in the design.
- Identify all system limits in the design.
- Use a trace table or another analytical method to verify the correctness of the high level design.
- Completeness:
- Verify that the design contains all possible tasks to be accomplished by the software.
- Verify that the design contains all possible workflows necessary to accomplish the tasks.
- Verify that the design contains all user interface items necessary to handle the workflows.
- Verify that the design covers all applicable requirements.
- Verify that the design allows all specified outputs to be produced.
- Verify that the design requires all needed inputs to be furnished.
- Verify that all required includes/imports are stated.
- User Interface Analysis:
- Build mock-ups of the user interface, even if it’s a command-line interface.
- Build mock-ups of the user interface including the accessibility, internationalization, and localization requirements.
- User Experience Analysis:
- Verify that the documented workflows will work.
- If possible, do user studies with whatever tools are available (role play, mock-ups, a combination of the two, etc.).
- If possible, do user studies with users that will require the accessibility, internationalization, and localization features.
- External Limits (Where Applicable):
- Document all external limits.
- Determine if behavior is correct at nominal values.
- Determine if behavior is correct at limits.
- Determine if behavior is correct beyond limits.
- Logic (from Correctness by Construction):
- Use a trace table, mathematical proof, or similar method to verify the logic.
- Verify that program sequencing is proper.
- Verify that data structures are in the proper order.
- Verify that recursion has a base case.
- Verify that recursion unwinds properly.
- Check for and document any accidental indirect recursion.
- Verify that all loops are properly initiated, incremented, and terminated (invariants, variants, postconditions, and preconditions are correct).
- Examine each conditional statement and verify all that cases are complete and correct.
- Examine each conditional statement and verify all that cases are complete and correct.
- Add asserts, preconditions, and postconditions for all assumptions in functions.
- Add asserts, preconditions, and postconditions for all uses of libraries.
- Add invariants for all assumptions on data structures.
- State Machine Analysis:
- For each state machine, verify that the state transitions are complete and orthogonal.
- Document each state machine.
- Internal Limits (Where Applicable):
- Document all internal limits.
- Determine if behavior is correct at nominal values.
- Determine if behavior is correct at limits.
- Determine if behavior is correct beyond limits.
- Special Cases:
- Check all special cases.
- Ensure proper operation with empty values for all variables.
- Ensure proper operation with full values for all variables.
- Ensure proper operation with min values for all variables.
- Ensure proper operation with max values for all variables.
- Ensure proper operation with negative values for all variables.
- Ensure proper operation with zero values for all variables.
- Protect against out-of-limits conditions.
- Protect against overflow/underflow conditions.
- Ensure that all “impossible” conditions are documented and absolutely impossible.
- Handle all possible incorrect or error conditions and document them.
- Data Structures:
- Verify that all data structures are fully understood.
- Verify that all data structures are properly documented.
- Verify that all data structures are properly used.
- Functions/Procedures:
- Verify that all functions/procedures are fully understood.
- Verify that all functions/procedures are properly documented.
- Verify that all functions/procedures are properly used.
- Security Considerations:
- Verify that all security-sensitive data are from trusted sources.
- Verify that the threat model is complete and comprehensive.
- Verify that the list of attacks is comprehensive for the threat model.
- For each type of attack, verify that the software properly mitigates, or eliminates, threats of all attacks.
- Safety Considerations:
- Verify that the software does not cause system limits to be exceeded.
- For each safety requirement, verify that the software does not violate that requirement.
- Names:
- Verify that all special names are clear, defined, authenticated, and documented.
- Verify that the scope of all variables and parameters are defined and documented.
- Verify that all named items are used within their declared scopes.
- Standards:
- For each applicable design standard, verify that the design conforms to the design standard.
- Dependencies:
- For each existing dependency, identify any needed update.
- For each missing dependency, identify a dependency that meets the requirements.
- Check:
- For each design defect, fix the defect and check that the fix is correct.
- If there were defects, go back to the beginning of the Design Review activity and start again.
- Check all design documents into the software repository.
- Preparation:
- Specification: To ensure that a specification of the software is
documented.
- Decide what language the specification will be written in, as well as any languages it will be translated into.
- Write the specification in the chosen primary language.
- Translate the specification into the other languages.
- Check the specification and translations into the software repository.
- Specification Review: To ensure that the specification is understood
by all programmers and that it meets the expectations of shareholders.
- Shareholder Review:
- Have every shareholder review the specification and submit questions.
- Update the specification and translations to answer the questions.
- Have every shareholder submit desired changes.
- Negotiate changes with all shareholders.
- Make the negotiated changes to the specification and all translations.
- Repeat the above five steps until there are no more questions or disagreements.
- Programmer Review:
- Have every programmer review the specification and submit questions.
- Update the specification and translations to answer the questions.
- Have every programmer submit objections.
- Take the objections to the shareholders for renegotiation.
- Make the negotiated changes to the specification and all translations.
- Repeat the above five steps until there are no more questions.
- Shareholder Review:
- Formal Specification: To ensure that a specification of the software
is formalized.
- Eecide on the tools that will be used to create the formal specification.
- Create the model.
- For each behavior in the model, create assertions about the behavior.
- For each limit, create assertions about the limit.
- Do all other work necessary to create the specification.
- Formal Specification Review: To ensure the correctness of the formal
specification according to the decided-on design.
- Limits:
- For each external limit, verify that the specification checks the limit.
- For each internal limit, verify that the specification respects the limit.
- For each system limit, verify that the specification respects the limit.
- Logic:
- For each loop, verify that the formal specification correctly describes the loop.
- For each conditional statement, verify that the formal specification correctly describes the conditions and the cases.
- For each instance of recursion, verify that the base case and non-base case are correctly described and separated.
- Verify that all state is correctly described.
- State Machines:
- For each state machine, verify that the specification properly specs the state machine.
- Limits:
- Formal Specification Test: To ensure that the design does not have
defects by testing the formal specification of the design.
- Model Checking (Where Applicable):
- If the formal specification is written in a language that supports model checking, run the model checker on the specification.
- Fix all defects.
- Formal Proof (Where Applicable):
- If the formal specification is written in a language that supports formal proofs, and doing a proof is feasible, prove the correctness of the specification.
- Fix all defects.
- Model Checking (Where Applicable):
- Test Design: To design the testing regimen for the software.
- Requirements:
- For each non-functional requirement, identify and document whether the requirement is worth testing.
- For each non functional requirement worth testing, identify and document at least one test method for testing the requirement.
- Paths:
- For each loop, identify and document paths to get zero, one, and many iterations.
- For each recursion, identify and document paths to get zero, one, and many recursions.
- For each condition, identify and document paths to reach all cases.
- Identify and document interaction between paths to identify all possible paths, including which paths depend on other paths.
- For all paths, identify and document whether the path is worth testing.
- For all paths worth testing, identify and document at least one method to test that path.
- Tools:
- For all test methods, identify and document a tool needed to accomplish that method.
- For all tools that exist, ensure that access is available.
- For all tools that don’t exist, design the tool using the Design checklists.
- Tests:
- For each non-functional requirement worth testing, design and document at least one test for each method that was decided to test the requirement.
- For each path worth testing, design and document at least one test for each method that was decided to test the path.
- Requirements:
- Test Design Review: To ensure that the testing regimen design is
complete, viable, and correct.
- Tools:
- For all tools that do not exist, review the design of the tool using the Design Review checklist.
- Complete:
- Verify that the list of paths is complete and comprehensive.
- For each test, verify that the test completely tests the path it is supposed to test.
- Viable:
- For each path worth testing, verify that at least one test method is viable for testing that path.
- For each test method, verify that the tool used to accomplish that method is viable and sufficient.
- Tools:
- Design: To design the software (or changes to the software).
- Code: To actually create the software.
- Update Dependencies: To update all existing dependencies.
- Identify each existing dependency.
- For each existing dependency:
- Determine the correct version to update to.
- Do a full code inspection of the changes to the new version of the dependency.
- Do a full inpection of the dependency’s test suite to ensure it is good enough.
- Vet the new version for correctness by using its test suite.
- Ensure the dependency is correctly hooked into the build.
- Remove New Technical Debt: To remove all new technical debt caused by
the design of the project.
- Identify each item of technical debt.
- For each item of technical debt:
- Remove the item.
- Make any changes required to fix the build.
- Make any changes required to fix the tests.
- For all changes:
- Write necessary design comments.
- Write necessary why comments.
- Write necessary teacher comments.
- Write necessary checklist comments.
- Write necessary debt comments.
- Split the code into code paragraphs and write necessary guide comments.
- Formatting:
- Format the code, usually with an automatic formatter.
- Add Dependencies: To add any new needed dependencies.
- Identify each dependency that the new code will need.
- For each new dependency:
- Determine the correct version to use.
- Do a full code inpection of the correct version of the dependency.
- Do a full inspection of the dependency’s test suite to ensure it is good enough.
- Vet the correct version for correctness using its test suite.
- Add the dependency to the build.
- Code: To actually code the software (or changes to the software).
- Form:
- For each function/procedure in the design, create a stub for the function/procedure and write the documentation comment for it.
- For each data structure in the design, create the data structure and document it with documentation comments.
- For each data structure in the design, write the data structure’s invariant(s) down as assertions in the functions/procedures that operate on the data structure.
- For each function/procedure in the design, write the preconditions and postconditions down as assertions in the function/procedure.
- For each function/procedure:
- Fill in the function/procedure.
- For each loop, write down the postconditions(s), precondition(s), invariant(s), and variant(s) as assertions.
- For each conditional statement, verify that the condition(s) are mutually exclusive and complete, including “impossible” conditions, which should be written down as negative assertions.
- Write down all remaining assumptions as assertions.
- For each basic block, put a failing assert at the beginning of the block.
- For all code:
- Write necessary design comments.
- Write necessary why comments.
- Write necessary teacher comments.
- Write necessary checklist comments.
- Write necessary debt comments.
- Split the code into code paragraphs and write necessary guide comments.
- Formatting:
- Format the code, usually with an automatic formatter.
- Form:
- Code Inspection: To have every programmer inspect the code they each
produced during the previous activity for defects.
- Complete:
- Verify that the code covers all of the functional requirements.
- Verify that the code covers all of the design.
- Includes/Imports:
- Verify that the includes/imports are complete.
- Initialization:
- Check variable, const, and parameter initialization at program start.
- Check variable, const, and parameter initialization at the start of every loop.
- Check variable, const, and parameter initialization at function/procedure entry.
- Names:
- Check name spelling and uses.
- Check consistency of names.
- For each name, check that the name is within the declared scope.
- For each name, check that the name does not shadow another name.
- For each struct member reference, verify that the correct operator
(
->
or.
) is used. - Check that all non-changing variables are declared
const
, including all levels for pointers (data and pointer areconst
), and even further for pointers to pointers, etc. - Check for
void
in the header of every procedure that does not take arguments (C only).
- Strings:
- Check that all strings are identified by pointers.
- Check that all strings have a length prefix.
- Check that all strings are terminated by a
nul
character.
- Assertions:
- Check that all preconditions exist in the code as complete and correct assertions.
- Check that all postconditions exist in the code as complete and correct assertions.
- Check that all loop postconditions exist in the code as complete and correct assertions.
- Check that all loop preconditions exist in the code as complete and correct assertions.
- Check that all loop invariants exist in the code as complete and correct assertions.
- Check that all loop variants exist in the code as complete and correct assertions.
- Check that all data structure invariants exist in the code as complete and correct assertions.
- Check that the preconditions for libraries exist in the code as assertions right before every call to those libraries.
- Check that the postconditions for libraries exist in the code as assertions right after every call to those libraries.
- Check that all other assumptions exist in the code as complete and correct assertions.
- Operators:
- Verify proper use of
=
. - Verify proper use of
==
,!=
,<
,<=
,>
,>=
,&&
, and||
. - Verify proper use of bitwise operators, including checking for overflow and underflow.
- Verify absence of divide-by-zero through checks, assertions, or other techniques.
- Verify proper use of non-wrapping arithmetic operators, including checking for overflow and underflow, asserting its impossibility, or using wrapping operators.
- Verify proper use of wrapping arithmetic operators or functions.
- Verify proper use of
()
to adjust precedence.
- Verify proper use of
- Calls:
- Check function call formats for correct usage of pointers.
- Check function call formats for correct usage of parameters.
- Check function call formats for correct usage of address-of operator.
- Recursion:
- Check for, and document, any indirect recursion. Use a tool if necessary.
- Check that all recursion has a base case (will terminate).
- Check that the condition for the base case is complete and correct.
- Check that the normal case unwinds the stack properly.
- Loops:
- For each loop, verify that the loop’s preconditions are met.
- For each loop, verify that the loop meets its postconditions.
- For each loop, verify that the loop meets its invariants.
- For each loop, verify that the loop properly updates its variants.
- Conditionals:
- For each conditional, check that the condition is complete and correct.
- Sequencing:
- Check for proper sequencing of program statements.
- Check for any sequence points undefined behavior (C only).
- Memory Safety:
- Check that all pointers are initialized
NULL
. - Check that all pointers are not
free()
‘ed before allocation. - Check that allocated pointers are always
free()
‘ed (no memory leaks). - Check that all allocated pointers are only
free()
‘ed once (no double frees). - Check that all allocated pointers are not used after they are
free()
‘ed (no use-after-free). - Check for possible stale pointers (pointers to “borrowed” data that might outlive the data).
- Check that all possible buffer accesses are either protected by bounds checks or that a buffer overrun is impossible.
- Check that all pointers are initialized
- Concurrency:
- Check for possible data races.
- Check for possible race conditions.
- Check for improper thread use (invalid joins, etc.).
- Output:
- Check that line stepping is proper.
- Check that spacing is proper.
- Files:
- Verify that all files are properly declared.
- Verify that all files are properly opened.
- Verify that all files are properly closed.
- Cryptography (Where Applicable):
- Check that all cryptographic operations are constant-time.
- Check that all buffers are zero’ed after use.
- Check that the compiler cannot optimize out the buffer zero’ing.
- If possible, check that all registers are cleared.
- Check that all secrets are wiped from memory.
- User Interface:
- For each user interface item, verify that it meets applicable requirements.
- For each user interface item, verify that it meets applicable accessibility requirements.
- For each user interface item, verify that it meets applicable internationalization requirements.
- For each user interface item, verify that it meets applicable localization requirements.
- User Experience:
- For each task and workflow, verify that the workflow will accomplish the task.
- For each task and workflow, do a user study to ensure that the users can easily accomplish the task with the workflow.
- Other Requirements:
- For each algorithm in the code, verify that it meets applicable time algorithmic requirements.
- For each algorithm in the code, verify that it meets applicable space algorithmic requirements.
- For each security requirement, verify that nothing in the code violates that requirement.
- For each safety requirement, verify that nothing in the code violates that requirement.
- Documentation:
- For each data structure, ensure that the data structure is properly documented, including member names and types, as well as invariants.
- For each function/procedure, ensure that the function/procedure is properly documented, including parameter names and types, as well as preconditions and postconditions.
- For each task and workflow, ensure that the task and workflow are documented.
- Syntax:
- Check that all
()
are proper and matched. - Check that all
[]
are proper and matched. - Check that all
{}
are proper and matched. - Check for proper punctuation.
- Check for other syntax errors.
- Check that all
- Standards:
- For each applicable standard, verify that the code conforms to the standard.
- Style:
- Check that all code matches the required style or run a formatter.
- Complete:
- Code Review: To allow every programmer on the team to review the code
written by every other programmer.
- For Each Programmer:
- For each other programmer, have the first programmer review the code from the second according to the Code Inspection checklist.
- Review the code as a team.
- For Each Programmer:
- Update Dependencies: To update all existing dependencies.
- Compile: To compile the code and remove as many defects as possible before
testing.
- Build Process: To create the build process used to compile the
software.
- Build Options:
- Using the design, a list of build options should be created.
- For each build option, add the build option to the build process.
- Add all material that must be generated to the build process.
- For Each Programmer:
- The programmer should ensure that the files that he wrote the majority of are added to the build process.
- The programmer should ensure that the dependencies of the files are properly resolved.
- Final:
- Do the rest of the work needed to create the build process.
- Review the build process for correctness.
- Build Options:
- Compile: To get the software properly compiling.
- Compile the software, logging the location and time spent fixing for all defects.
- Run one or more linters (like
clang-tidy
) on the code, logging the location and time spent fixing for all defects. - Run one or more static analyzers (like
scan-build
and/or Frama-C) on the code, logging the location and time spent fixing for all defects. - Report all defects to all programmers.
- Build Process: To create the build process used to compile the
software.
- Test: To test the software and validate that it meets its requirements.
- Create Test Tools: To create needed test tools.
- For Each Non-Existent Test Tool:
- Create the test tool with the Code phase checklists.
- Compile the test tool with the Compile phase checklists.
- Test the test tool with the Test phase checklists.
- For Each Non-Existent Test Tool:
- Test: To create the test suite and test the software thoroughly.
- Unit Tests:
- Write all unit tests that cannot be generated.
- Generate unit tests that can be generated (usually with property-based testing or fuzzing).
- Run all unit tests, removing basic block assertions as necessary, fixing bnd logging defects as they are found, and saving all generated tests that found one or more defects.
- Integration Tests:
- Write all integration tests that cannot be generated.
- Generate integration tests that can be generated (usually with property-based testing or fuzzing).
- Run all integration tests, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- System Tests:
- Write all system tests that cannot be generated.
- Generate system tests that can be generated (usually with property-based testing or fuzzing).
- Run all system tests, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- Error Tests:
- Write all error tests that cannot be generated.
- Generate error tests that can be generated (usually with property-based testing or fuzzing).
- Run all error tests, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- Performance Tests:
- Write all tests for performance requirements that cannot be generated.
- Generate all tests for performance requirements that can be generated (usually with property-based testing or fuzzing).
- Run all tests for performance requirements, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- Security Tests:
- Write all tests for security requirements that cannot be generated.
- Generate all tests for security requirements that can be generated (usually with property-based testing or fuzzing).
- Run all tests for security requirements, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- Safety Tests:
- Write all tests for safety requirements that cannot be generated.
- Generate all tests for safety requirements that can be generated (usually with property-based testing or fuzzing).
- Run all tests for safety requirements, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- Cryptography Tests (Where Applicable):
- Write all tests for constant-time cryptography that cannot be generated.
- Generate all tests for constant-time cryptography that can be generated (usually with property-based testing or fuzzing).
- For each platform, run all tests for constant-time cryptography multiple times, logging the min, max, mean, median, Q1, Q3, and standard deviation of time.
- For each test result, calculate the probability of whether the code is constant-time or not, using standard statistical formulas.
- For each constant-time defect, fix and log the defect as is is found and save all generated tests that found the defect.
- User Interface Tests:
- Write all tests for user interface requirements that cannot be
generated, (usually using
expect
for CLI’s or automatic GUI test frameworks). - Generate all tests for user interface requirements that can be generated (usually with user input recording or macros).
- Run all tests for user interface requirements, removing basic block assertions as necessary, fixing and logging defects as they are found, and saving all generated tests that found one or more defects.
- Write all tests for user interface requirements that cannot be
generated, (usually using
- Coverage:
- Verify that branch coverage is what was expected with the ratio of paths that were deemed not worth testing.
- Verify that the line coverage is what was expected with the ratio of paths that were deemed not worth testing.
- Design tests to cover missing branches and lines and log the test defects.
- Create and run the tests to cover the missing branches and lines.
- Run an automated mutation-driven testing tool to identify more coverage problems.
- Add tests to cover the holes found by the mutation-driven testing tool and log the defects as Test defects.
- Remove basic block assertions as necessary.
- Fix and log the defects found by the new tests.
- Unit Tests:
- Additional Testing: To use the existing test suite to test the
software further.
- Dynamic Analysis:
- For each test, decide whether it is possible to run the test under the dynamic analyzers such as ASan, UBSan, MSan, and TSan.
- For each dynamic analyzer, run all possible tests.
- Fuzzing:
- For each test, decide whether it is possible to use it as input to fuzzers.
- For each fuzzer, create starting test cases from the possible test cases.
- For each fuzzer, run the fuzzer on the software.
- Dynamic Analysis:
- Create Test Tools: To create needed test tools.
- Documentation: To ensure the documentation is complete, useful, and
captures as much knowledge about the system as possible from the heads of the
programmers.
- Documentation Tests: To gather requirements for documentation and
ensure those requirements will be met.
- Documentation Requirements:
- Create the requirements for the reference documentation.
- Create the requirements for the workflows.
- Create the requirements for the explanations/discussions.
- Create the requirements for the tutorials.
- Create the requirements for the how-to guides.
- Create the requirements for the introductions.
- Create the requirements for the case studies.
- Documentation Tests:
- Implement any possible tests for the requirements of the reference documentation.
- Implement any possible tests for the requirements of the workflows.
- Implement any possible tests for the requirements of the explanations/discussions.
- Implement any possible tests for the requirements of the tutorials.
- Implement any possible tests for the requirements of the how-to guides.
- Implement any possible tests for the requirements of the introductions.
- Implement any possible tests for the requirements of the case studies.
- Documentation Requirements:
- Reference Documentation: To complete the reference documentation.
- Gather all documentation.
- Sort the Non-Reference Documentation:
- Identify all existing documentation that is not reference documentation.
- For each bit of non-reference documentation, figure out what kind of documentation it is.
- For each bit of non-reference documentation, put it with that type of documentation.
- Write a “Ground Truth” Document:
- Identify what the software actually does, including corner cases.
- Document what the software actually does.
- Update pre-existing reference documentation.
- Reference Documentation Review:
- Review the “Ground Truth” document for correctness and completeness.
- Review the rest of the reference documentation for correctness and completeness.
- Ensure that the reference documentation passes the relevant tests.
- Workflows: To update and create workflows.
- Update Workflows:
- Identify workflows that existed before starting the project.
- For each pre-existing workflow, update it or remove it, as applicable.
- Identify workflows created as part of this project.
- For each workflow, ensure it is correct.
- Review Workflows:
- Ensure that workflows cover all important and common user tasks.
- Review all workflows for completeness and correctness.
- Ensure that the workflows pass the relevant tests.
- Update Workflows:
- Explanations/Discussions: To update and create
explanations/discussions.
- Update Explanations/Discussions:
- Identify existing explanations and discussions.
- For each existing explanation or discussion, update it or remove it, as applicable.
- Write Explanations/Discussions:
- Identify concepts that users must comprehend in order to understand the reference documentation.
- For each concept, write the requisite explanation and/or discussion.
- Review Explanations/Discussions:
- Ensure that explanations and discussions cover all concepts necessary to understand the reference documentation.
- Review all explanations and discussions for completeness and correctness.
- Ensure that the explanations and discussions pass the relevant tests.
- Update Explanations/Discussions:
- Tutorials: To update and create tutorials.
- Update Tutorials:
- Identify existing tutorials.
- For each existing tutorial, update it or remove it, as applicable.
- Write Tutorials:
- For each new workflow, write a tutorial for it.
- Identify concepts that users must comprehend in order to understand the reference documentation.
- For each concept, identify the explanation and/or discussion.
- For each explanation or discussion, identify whether diving deep might help users.
- For each needed deep dive, write the requisite tutorial.
- Review Tutorials:
- Ensure that tutorials cover all concepts necessary to understand the reference documentation and explanations/discussions.
- Review all tutorials for completeness and correctness.
- Ensure that the tutorials pass the relevant tests.
- Update Tutorials:
- How-to Guides: To update and create how-to guides.
- Update How-to Guides:
- Identify existing how-to guides.
- For each existing how-to guide, update it or remove it, as applicable.
- Write How-to Guides:
- Identify common problems that users might be facing.
- For each common problem, write a how-to guide showing how to solve it with the software.
- Review How-to Guides:
- Ensure that how-to guides cover all common problems that users might face.
- Review all how-to guides for completeness and correctness.
- Ensure that the how-to guides pass the relevant tests.
- Update How-to Guides:
- Introductions: To update and create introductions.
- Update How-to Introductions:
- Identify existing introductions.
- For each existing introduction, update it or remove it, as applicable.
- Write Introductions:
- Identify new features and changes that need justification.
- For each feature or change, write the requisite introduction.
- Review Introductions:
- Ensure that introductions cover all new features or changes that need justification.
- Review all introductions for completeness and correctness.
- Ensure that the introductions pass the relevant tests.
- Update How-to Introductions:
- Case Studies: To update and create case studies.
- Update Case Studies:
- Identify existing case studies.
- For each existing case study, update it or remove it, as applicable.
- Write Case Studies:
- Identify anything in the software that benefits users and is appropriate for a case study.
- For each such thing, write the requisite case study.
- Review Case Studies:
- Ensure that case studies cover at least the best benefits for users.
- Review all case studies for completeness and correctness.
- Ensure that the case studies pass the relevant tests.
- Update Case Studies:
- Announcements: To create announcements.
- Add to the Changelog.
- Write the Release Notes.
- Write Announcements:
- Answer the “who”.
- Answer the “what”.
- Answer the “when”.
- Answer the “where”.
- Answer the “why”.
- If useful, answer the “how”.
- Documentation Clean Up: To ensure the documentation is prepared for
public release.
- Proofreading:
- Ensure that the documentation has no grammar, spelling, or other errors.
- Formatting:
- Ensure that the formatting on the documentation is correct.
- Final Testing:
- Ensure that the documentation still passes all relevant tests.
- Proofreading:
- Documentation Tests: To gather requirements for documentation and
ensure those requirements will be met.
- Postmortem: To wrap up loose ends and gather data about the process.
- Postmortem: To complete the personal and team postmortems.
- Personal Postmortem:
- Have every programmer complete a postmortem for themselves alone.
- Team Postmortem:
- Gather the programmers.
- Complete a general postmortem for the project.
- Personal Postmortem:
- Finish Project: To make the software available.
- Merge (Where Applicable):
- Merge the subproject into
master
or the appropriate branch.
- Merge the subproject into
- Release.
- Merge (Where Applicable):
- Postmortem: To complete the personal and team postmortems.
Analysis
This checklist is just a formatted and cut down version of my personal software process.
The reason I used my personal process is because I don’t know of other processes that are documented to the detail that mine is. People will probably disagree with mine, and that’s fine. I just want the industry to agree on a Standard of Care that is at least at a certain acceptable level.
Also, I couldn’t be bothered to come up with something new.
But this process is optimized for programming in C. I would love to see separate standards of care for different programming languages, and I think that professional programmers would have to qualify for each language they might use.
The standard of care for C will be far higher than that of Rust or Python, for example.
But there should be a lot of things in common between languages; software development has many common themes no matter the language.
Also, the most important thing about this process is the discipline it demands.
I mentioned above that gaining discipline is our first step to becoming professional. This proces will serve it in spades.
First, you have to actually sit down and articulate the problem and the requirements to solve it. Then you must design first without writing any code. Then you must design the testing regimen and ensure everything will be tested.
However, I am not attached to this process; I think many different processes could serve, such as the Agile-like one suggested by Uncle Bob in the Voxxed CERN 2019 Keynote.
Though I think there should be room for longer-term projects than just one or two weeks.
We just have to have something, a process by which work can be objectively measured.
Conclusion
I hope that, by this point, it is clear that the title/thesis of this post is true: if we don’t become professionals, we may destroy everything we love and depend on.
I also hope that I have continued the debate over professionalizing the industry and provided a springboard with which to begin, as well as a way for good peer pressure to influence programmers the world over.