This article is based on and expanded from a series of talks by Robert C Martin (aka Uncle Bob) on the topic of software professionalism.
You are employed because of your competence. You are employed to be responsible and professional.
Professionalism earns respect, which you deserve.
There are rules to being a professional, and in the software industry these rules are not written yet but are beginning to be understood.
Here is a take on these rules.
Do Not Ship Shit.
Do not ship shit code you know could break. This does not mean unknown bugs as they are unknown. If you know it could break, it is shit code.
- Fragile code is shit
- Spaghetti "big ball of mud" code is shit.
- Untested (and therefore unproven) code is shit.
If the deadline is looming and the pressure to deploy is mounting, you must find some way to meet it without shipping shit; or you feed back that you can't make the deadline, and have a professional conversation about estimates and reality.
The business will not respect you for meeting a deadline with shit code that you then spend weeks fixing while your customers watch. Those customers expect the product to work! If it doesn't because you shipped shit, how can you deserve respect. Can you see the problem?
Do not suggest or accept any kind of "stabilisation phase" of monitoring!
Do not deploy code into a production environment that you monitor for a few days/weeks hoping it doesn't fail. The code you allowed to be deployed is untrusted, it is probably untested. The code is shit.
Always Be Ready.
At the end of each iteration, whatever the length, you should be ready to deploy something. It may not be a complete feature, but must be a separately deployable item of functionality.
If the business decides there aren't enough features to deploy, then that is an acceptable business decision and not of your concern.
Your code should be ready to deploy when it is "done".
- Done means fully tested with TDD. This does not mean coverage but functionality.
- Done means independently tested by another individual not in your engineering team.
You must always be ready to start development of a new item of work. What if you have become blocked? You can't just sit there with nothing to do. The phrase "Never be blocked" is relevant here as there should always be something you can work on, preferably for the current team goal.
Always be ready to continue where you left off. When you finish another piece of work, become unblocked, your workspace should be ready to continue. That means you can 'reset' to the work you have to do next.
Little And Often.
Commit your code little and often as this will be a huge aid for rule 1. Affect one or two files then commit.
You should be working in branches, so who cares if it isn't complete within a single commit? Large commits of tens of files make it a certainty that no one will review your code properly or effectively.
If you use some kind of task management system then reference the completed or relevant tasks in the small commits. This makes it easy for people to follow what you are doing.
The speed at which you produce working code must be stable throughout the life of a project. This allows consistent productivity within the project, not slowdown while fixing rushed, shit code.
You must be able to produce working code at the same speed at any stage within a project; even after the project has "finished".
Don't rush a start by ignoring the 'boring' stuff like design discussions, only to end up slow and defeated. It is an inevitability. The boring stuff is most important and prevents you from winging it and producing shit.
Do not make unstable code that will harm yours and others productivity; see rule 1. Stable productivity means that you can confidently move from task to task, knowing the previous is satisfactorily complete for the known use cases. That includes tests.
A full suite of tests for the functionality means that another team member can pick up where you have left off with confidence.
The code you write must be adaptable by you or another team member, with ease and at low cost.
To achieve this, you must have properly abstracted your code, or prepared for that abstraction by using design principles. This doesn't mean ignoring the YAGNI principle, but for example a database must always live behind an abstraction even if it is unlikely to be replaced.
Learning when to make abstractions for adaptability and when not to makes a professional.
Every time you alter code, leave it cleaner than when you started; this is not the kind of refactoring that worries the business - it is your duty as a professional. If the Scouts can do it, so can you.
Write tests for non-tested code and if you can't, alter the code and move it towards testable code; even a small move towards testability in an iteration is an improvement.
You must follow TDD for all new production code. There is always the argument banded around that TDD can't always be done, and this is possibly true when prototyping an idea or proving a design theory.
When that theory (or prototype) is proven throw that code away and write the tests, then write the code to fulfil the tests using the knowledge gained by the prototype, that way you already know how to solve the problem in a fearless manner. You will be surprised by how much less code you write when backed by tests than was in your prototype or design.
You must attempt TDD for existing code that is testable in theory but has no existing tests. If you really can't follow TDD because the code is completely untestable, see rule 6.
(The secret to TDD is to only test the public interface. Testing all scenarios of that interface should touch all the code and classes beneath it.)
Follow the SOLID principles as best you can. Programming is about compromises and you won't get 100% compliance, but any is better than none.
Abstract the code into more classes and interfaces where use cases mean more than one possible interpretation of implementation. This does not go against the 'YAGNI' principle, it only strengthens it; You Ain't Gonna Need It (yet), but you need to be prepared for when you do.
Abstract those classes into methods as short as 1 to 4 lines whenever you can. Name them appropriately. Make your code read like prose; it should tell you in plain English what it is doing by its name alone.
All unit tests must run in isolation from other tests and test cases. All set up for a test case must be torn down afterwards. Do not have one test set up a state to be used in another, this is a cardinal sin and leads to shit code. Shit tests are shit code.
We will not dump on QA.
This is an extension in principal to rule 1. Do not pass code to QA that you haven't thoroughly tested yourself. Care about your code and equally the time and effort of your colleagues.
QA are not your safety net - they are there to prove you right. You should have proved yourself wrong already by unit, integration and behavioural tests and fixed your code.
QA will find nothing.
QA should approach their testing with the challenge of finding interesting and creative ways to cause bugs. They should never find quite obviously broken functionality.
Never expect to hear "it just doesn't work".
Act like a surgeon. If you installed a pace-maker, hearing "it just doesn't work" would be a real problem, no?
QA should only find bugs that were a struggle to cause as your functionality must already work as expected. It is QA's job to make sure the defined business rules are implemented and make sense. If they find bugs, they should be with the business rules themselves and not your implementation of them.
Automation! Automation! Automation!
Automate all of the things.
Set up your workspace to automate unit testing whenever you save a test file. If you can't do that, then get a better workspace.
Set up your repositories to automate unit and integration tests, and behavioural tests if you have them. It is not super simple to do, but invest the time and it will pay dividends even in the short term.
Continuous integration and deployment is not easy. Doing it effectively is even harder - you have to have aligned nearly all of your product's working parts. Again, invest the time to at least lay out a plan to do it, and work fearlessly toward that goal.
Do not allow fragile code to pass to production. Fragile code is shit code. Fragile code has holes in its testing. It has insufficient logging (and by the way, logging everything is insufficient logging). It doesn't know what to do when an error happens, and just shits all over the user. Don't be the engineer who says "I don't know" when asked what happened to cause a scenario.
It takes effort and thought to know what to log and how to handle errors. It means you have to group-think what possible errors could happen (without discussing meteors and tsunamis at your data-centre, stick to the code). It means you have to know your code. The easy way to know your code is by having well written interfaces and classes, and plain and clear tests.
Cover for each other.
It is your responsibility as part of the team to be competent in the system you are working on. Never say things like "The database isn't my responsibility" or "I didn't work on that part"; find out so that when someone goes down (which they inevitably will), you can get it done.
Finding out means talking with your colleagues about what they are working on and how they are planning on implementing the designs.
Know your code. Your code is the team's code, and the team's code is your code.
The most honest estimate is "I don't know".
Estimates are about accuracy and precision; estimates may have a wide timescale dependent on current knowledge and become more refined as time passes and more knowledge and detail is gained.
Do not refine estimates by guessing, this is unprofessional.
It is human to wildly mis-estimate things; it's in our nature and is not something you can learn to fix, so don't try. Be professional and honest about the inaccuracy of estimates.
Never leave yourself open to misinterpretation; giving an answer like "Maybe a month" will be heard as "A month". Best case and worse case scenarios work well.
You should say "No".
Learn the art of saying no.
If you know that a sprint, iteration or project cannot be done by a requested date, you must say no. Never say "Ok, I will try." You are already trying, you are trying your best every single day (or you bloody should be); if it can't be done, it can't be done.
Saying no makes you a team player because you are saving the team from the embarrassment of breaking these rules, and saving the business from embarrassment in front of customers. It may look like you are being difficult, but show others that you are a professional by sticking to your answers unless new information comes to light.
Changing your mind when asked repeatedly (which will happen), or being ground down into giving an answer someone wants to hear so they leave you alone is not professional, it shows weakness of character and you will not earn respect as a professional software engineer.
You are hired for your expertise on software. Deadlines might not be adjustable. Budget might not be increasable. If you say no, try to have one or more proposals for alternatives. If you have no alternatives, then you must ask for time to find one.
Tight budgets and deadlines are not unique to software companies; Doctors and Accountants have the same! An answer is always there, it just needs looking for, but remember cardinal rule 1: Do not ship shit. Do not meet deadlines by allowing shit code to happen.
Continuous Aggressive Learning.
You should expect support in your learning with materials and conferences. It is expected that in return, you learn.
If you don't get support for learning, ask for it. If you are told "No", then maybe it is time to look for another job.
You must provide time outside of work hours to learn; it is your career, profession and responsibility; this takes aggressive effort on your part. There is no 9-to-5 in software development, that's just the way it is. It's the same in other professions like accountancy, medicine, legal and many more.
If you aren't willing to learn in the fastest evolving profession in history, you will be obsolete in a very short time, with no one to blame but yourself. If you find yourself being sidelined into dead end work instead of working on new products, then take a look in the mirror and ask yourself "What can I learn today that will make me relevant?". If you are still not willing to learn, then this is not the career for you; go do something else.
Do not be fooled into believing that working on production code outside business hours is 'learning' and is going to progress your career. You won't be learning anything new, and it will only stifle you in the long run. A career is a long game.
Mentoring isn't about looking for that one person to teach you everything, it's a group effort to teach each other what individuals know. Don't expect someone to come and give you the answers; be engaged, ask questions if you're unsure or don't know.
It is better to feel a fool for a day by asking a question, than to be a fool for life by not.
Be a mentor on a subject you know well, but be open to correction. Be a person that your team come to with questions, but entertain discussion around the subject; even if you know you are right, others might not and will need guiding to the truth.
If you are asked a question you are unsure of the answer to, say so. If you know a colleague knows the subject better, point them out. This is the essence of rigorous mentoring. You know the right information is being spread rather than half answers.
Don't mentor based on opinions. Opinions are like arse holes, evetyobody has them... Keep in mind that experience is not the same as opinion; sometimes a person can't prove why they know something, so mind their experience and capabilities before judging what they say as opinion.
Perfectionism is not your friend.
Perfectionism will kill your productivity. With this in mind though, do not be fooled into taking short cuts to meet unreasonable deadlines. You will be making shit and it will hurt you.
There is a line of what is 'just enough' vs shit. It takes time to learn that line. Follow design principles, coding standards, and best practices like TDD and you will learn the line well.
Perfectionism locks us into questioning whether we have made the right decisions. If it works and the tests say so, it is just enough for now to keep momentum going. As long as you have followed a thought out design principal, you can always address sketchy coding decisions later.
Always aim for better code, but know that you will never achieve perfect code - it does not exist.
Why Write This Piece?
I have been doing this shit for a while now and I have fallen foul of many pitfalls along the way which has only solidified my resolve in expecting professionalism. I expect it of myself and my colleagues. I expect it of the industry as a whole.
I have seen so much code that is so bad it simple doesn't work and has proved dangerous to the business that allowed it's release. I've seen business' fail because of bad code.
Clean code is a must. Professionalism is a must.
'Developers' as we are called, do not get much respect and are treated as a lower class of monkey; (in the words of Uncle Bob) throw meat in a dark room of developers, get code out.
It has to change.
We hold the keys to the future as more and more of societies services become code driven. It is our responsibility to be professional. It is our moral duty to be.
This is not a rallying cry to be at odds with businesses and argue the toss like children, it is a rallying cry to grow up as an industry and be professional with our abilities. All professions started somewhere, and all have matured into respectable groups of talented individuals.
We must do the same.