— work, learning — 19 min read
Hi,
I am Aman, and this safe space is where I am most raw with my thoughts, hop on if you'd like to interact with them :))
This chapter will be different from the other chapters, the pragmatic programmer is the most phenomenal book I have read on professional software development.
This chapter will contain excerpts directly taken from the book and in no way covers the whole book, I personally think that the best way to learn from this book is to read it again and again like a text book patiently.
Programming is a craft. At its simplest, it comes down to getting a computer to do what you want it to do (or what your user wants it to do). As a programmer, you are part listener, part advisor, part interpreter, and part dictator. You try to capture elusive requirements and find a way of expressing them so that a mere machine can do them justice.
In order to be a Pragmatic Programmer, we’re challenging you to think about what you’re doing while you’re doing it. This isn’t a one-time audit of current practices—it’s an ongoing critical appraisal of every decision you make, every day, and on every project. Never run on auto-pilot. Constantly be thinking, critiquing your work in real time. The old IBM corporate motto, THINK!, is the Pragmatic Programmer’s mantra.
write code that’s easier to maintain, and spend less time in meetings.
A tourist visiting England’s Eton College asked the gardener how he got the lawns so perfect. “That’s easy,” he replied, “You just brush off the dew every morning, mow them every other day, and roll them once a week.” “Is that all?” asked the tourist. “Absolutely,” replied the gardener. “Do that for 500 years and you’ll have a nice lawn, too.” Great lawns need small amounts of daily care, and so do great programmers. Management consultants like to drop the word kaizen in conversations. “Kaizen” is a Japanese term that captures the concept of continuously making many small improvements.
Programmers take charge of their own career, and aren’t afraid to admit ignorance or error.
Responsibility is something you actively agree to. You make a commitment to ensure that something is done right, but you don’t necessarily have direct control over every aspect of it. In addition to doing your own personal best, you must analyze the situation for risks that are beyond your control. You have the right not to take on a responsibility for an impossible situation, or one in which the risks are too great, or the ethical implications too sketchy. You’ll have to make the call based on your own values and judgment.
Don’t be afraid to ask, or to admit that you need help.
I have previously talked about the importance of working with ambitious people and why you should care, this advice completely resonates with what I think about work.
One broken window, left unprepared for any substantial length of time, instills in the inhabitants of the building a sense of abandonment—a sense that the powers that be don’t care about the building.
Fix each one as soon as it is discovered.
Now that sounds pretty extreme. Surely the fire department’s first priority is to put out the fire, collateral damage be damned. But they clearly had assessed the situation, were confident of their ability to manage the fire, and were careful not to inflict unnecessary damage to the property.
Often you’ll be in situations where trade-offs are involved. Surprisingly, many users would rather use software with some rough edges today than wait a year for the shiny, bells-and- whistles version (and in fact what they will need a year from now may be completely different anyway). Many IT departments with tight budgets would agree. Great software today is often preferable to the fantasy of perfect software tomorrow. If you give your users something to play with early, their feedback will often lead you to a better eventual solution
Don’t spoil a perfectly good program by over embellishment and over refinement. Move on, and let your code stand in its own right for a while. It may not be perfect. Don’t worry: it could never be perfect.
Not everything can be done at once, the beauty of programming and professional software development lies in incremental iterations.It requires skill and effort to be good at communication with machine and oftentimes this is not a onetime process
Your ability to learn new things is your most important strategic asset. But how do you learn how to learn, and how do you know what to learn?
As the bar for learning goes down, learning something hard will not be a barrier anymore, how good people are at learning "x" will be more important.
A good idea is an orphan without effective communication.
KNOW YOUR AUDIENCE
To do that, you need to understand the needs, interests, and capabilities of your audience.
CHOOSE YOUR MOMENT
“Is this a good time to talk about...?’’
MAKE IT LOOK GOOD
Your ideas are important. They deserve a good-looking vehicle to convey them to your audience.
BE A LISTENER
Encourage people to talk by asking questions, or ask them to restate the discussion in their own words.
Not All Code Duplication Is Knowledge Duplication
1def validate_age(value):2validate_type(value,3)4validate_min_integer(value, 0)5
6def validate_quantity(value):7validate_type(value,8)9validate_min_integer(value, 0)
The code is the same, but the knowledge they represent is different. The two functions validate two separate things that just happen to have the same rules. That’s a coincidence, not a duplication.
Once you have your components mapped out, ask yourself: If I dramatically change the requirements behind a particular function, how many modules are affected? In an orthogonal system, the answer should be “one.’’ Moving a button on a GUI panel should not require a change in the database schema. Adding context- sensitive help should not change the billing subsystem. Also ask yourself how decoupled your design is from changes in the real world. Are you using a telephone number as a customer identifier? What happens when the phone company reassigns area codes? Postal codes, Social Security Numbers or government IDs, email addresses, and domains are all external identifiers that you have no control over, and could change at any time for any reason. Don’t rely on the properties of things you can’t control. Get into the habit of being constantly critical of your code. Look for any opportunities to reorganise it to improve its structure and orthogonality. Bug fixing is also a good time to assess the orthogonality of the system as a whole. When you come across a problem, assess how localised the fix is. Do you change just one module, or are the changes scattered throughout the entire system? When you make a change, does it fix everything, or do other problems mysteriously arise? This is a good opportunity to bring automation to bear.
Nothing is forever—and if you rely heavily on some fact, you can almost guarantee that it will change.
With every critical decision, the project team commits to a smaller target—a narrower version of reality that has fewer options.
Every engineering decision has a tradeoff modelling the messiness of the real world is not simple and it has it's own pros and cons
Tracer development is consistent with the idea that a project is never finished: there will always be changes required and functions to add. It is an incremental approach.
With a prototype, you’re aiming to explore specific aspects of the final system. With a true prototype, you will throw away whatever you lashed together when trying out the concept, and recode it properly using the lessons you’ve learned.
Prototypes are designed to answer just a few questions, so they are much cheaper and faster to develop than applications that go into production. The code can ignore unimportant details— unimportant to you at the moment, but probably very important to the user later on. If you are prototyping a UI, for instance, you can get away with incorrect results or data. On the other hand, if you’re just investigating computational or performance aspects, you can get away with a pretty poor UI, or perhaps even no UI at all.
What really helps me are comments, I use comments as notes so that I can revisit them later either when iterating or in self pr reviews.
Additionally it helps other people to understand what my base assmumptions are and just makes collaboration easy.
Computer languages influence how you think about a problem, and how you think about communicating.
We always try to write code using the vocabulary of the application domain
By learning to estimate, and by developing this skill to the point where you have an intuitive feel for the magnitudes of things, you will be able to show an apparent magical ability to determine their feasibility.
The first part of any estimation exercise is building an understanding of what’s being asked. As well as the accuracy issues discussed above, you need to have a grasp of the scope of the domain. Often this is implicit in the question, but you need to make it a habit to think about the scope before starting to guess. Often, the scope you choose will form part of the answer you give: “Assuming there are no traffic accidents and there’s gas in the car, I should be there in 20 minutes.”
This is the fun part of estimating. From your understanding of the question being asked, build a rough-and-ready bare-bones mental model.
Finally, the palest ink is still better than the best memory. Keep track of your thoughts and your history
As Pragmatic Programmers, our base material isn’t wood or iron, it’s knowledge. We gather requirements as knowledge, and then express that knowledge in our designs, implementations, tests, and documents. And we believe that the best format for storing knowledge persistently is plain text. With plain text, we give ourselves the ability to manipulate knowledge, both manually and programmatically, using virtually every tool at our disposal.
Every woodworker needs a good, solid, reliable workbench, somewhere to hold work pieces at a convenient height while they’re being shaped. The workbench becomes the center of the woodshop, the maker returning to it time and time again as a piece takes shape.
Thorsten talks about this in his newsletter which is further interpreted by prime
To quote prime directly
Thorston is like a 100x engineer and you can see it throughout this article can anyone guess what it is not only is he uh not afraid to try things he has like deep curiosity that makes him not just try it for a moment but try it and get really good at it and then go oh I like these things from it and I don't like these other things and like that is such a such a valuable skill
The biggest weakness I see in Engineers is uh the most evident when it comes to working with go which is they open up they start using go and when they start using go they go it doesn't have macros like rust does oh man it doesn't have some types like rust does and they they they try to take go and try to make it into rust and then when they're severely dis dis appointed that it doesn't do it how rust does it they're like this language sucks you could say the same thing about any of those any other languages when you compare them that is the single worst way to use and learn a language it's because you are trying to use the language as if it were rust it's not how does it propose you solve something it could be vastly different than what you're used to and maybe that difference is better maybe it's worse but you will never know unless if you understand how it wants to do something and that's the important part
Additionally I have expressed a notion similar to this in one of the previous chapters
First, look at yourself while you’re editing. Every time you find yourself doing something repetitive, get into the habit of thinking “there must be a better way.” Then find it.
Debugging is a sensitive, emotional subject for many developers. Instead of attacking it as a puzzle to be solved, you may encounter denial, finger pointing, lame excuses, or just plain apathy. Embrace the fact that debugging is just problem solving, and attack it as such. Having found someone else’s bug, you can spend time and energy laying blame on the filthy culprit who created it. In some workplaces this is part of the culture, and may be cathartic. However, in the technical arena, you want to concentrate on fixing the problem, not the blame.
It doesn’t really matter whether the bug is your fault or someone else’s. It is still your problem.
If your first reaction on witnessing a bug or seeing a bug report is “that’s impossible,” you are plainly wrong. Don’t waste a single neuron on the train of thought that begins “but that can’t happen” because quite clearly it can, and has.
’nuf said.
When you come across a surprise bug, beyond merely fixing it, you need to determine why this failure wasn’t caught earlier. Consider whether you need to amend the unit or other tests so that they would have caught it. Also, if the bug is the result of bad data that was propagated through a couple of levels before causing the explosion, see if better parameter checking in those routines would have isolated it earlier (see the discussions on crashing early and assertions here and here, respectively). While you’re at it, are there any other places in the code that may be susceptible to this same bug? Now is the time to find and fix them. Make sure that whatever happened, you’ll know if it happens again. If it took a long time to fix this bug, ask yourself why. Is there anything you can do to make fixing this bug easier the next time around? Perhaps you could build in better testing hooks, or write a log file analyser. Finally, if the bug is the result of someone’s wrong assumption, discuss the problem with the whole team: if one person misunderstands, then it’s possible many people do. Do all this, and hopefully you won’t be surprised next time.
However, the basic principle stays the same—when your code discovers that something that was supposed to be impossible just happened, your program is no longer viable.
Always take small, deliberate steps, checking for feedback and adjusting before proceeding. Consider that the rate of feedback is your speed limit. You never take on a step or a task that’s “too big.” What do we mean exactly by feedback? Anything that independently confirms or disproves your action.
Decoupled Code Is Easier to Change
1public void applyDiscount(customer, order_id, discount) {2totals = customer.orders.find(order_id).getTotals();3totals.grandTotal = totals.grandTotal - discount;4totals.discount = discount;5}
Ultimately our top-level code has to know that a customer object exposes orders, that the orders have a find method that takes an order id and returns an order, and that the order object has a totals object which has getters and setters for grand totals and discounts. That’s a lot of implicit knowledge. But worse, that’s a lot of things that cannot change in the future if this code is to continue to work. All the cars in a train are coupled together, as are all the methods and attributes in a train wreck.
This principle says that you shouldn’t make decisions based on the internal state of an object and then update that object. Doing so totally destroys the benefits of encapsulation and, doing so, spreads the knowledge of the implementation throughout the code.
All programs transform data, converting an input into an output. And yet when we think about design, we rarely think about creating transformations. Instead we worry about classes and doing. modules, data structures and algorithms, languages and frameworks.
We think that this focus on code often misses the point: we need to get back to thinking of programs as being something that transforms inputs into outputs.
When people first sit down to design an architecture or write a program, things tend to be linear. That’s the way most people think—do this and then always do that. But thinking this way leads to temporal coupling: coupling in time. Method A must always be called before method B; only one report can be run at a time; you must wait for the screen to redraw before the button click is received. Tick must happen before tock.
This approach is not very flexible, and not very realistic.
On many projects, we need to model and analyze the application workflows as part of the design. We’d like to find out what can happen at the same time, and what must happen in a strict order. One way to do this is to capture the workflow using a notation such as the activity diagram.
This is a point I highly resonate with and is very well highlighted by prime and Casey in this video:
Abstraction Bad? | Clean Code : Horrible Performance : (Clip) Interview
A large part of our job is dealing with existing code, often written by other people. Those people will have different instincts to you, and so the decisions they made will be different. Not necessarily worse; just different.
Finding an answer that happens to fit is not the same as the
Don’t Program by Coincidence
At all levels, people operate with many assumptions in mind— but these assumptions are rarely documented and are often in conflict between different developers. Assumptions that aren’t based on well-established facts are the bane of all projects.
Always be aware of what you are doing. Fred let things get slowly out of hand, until he ended up boiled, like the frog here.
Can you explain the code, in detail, to a more junior programmer? If not, perhaps you are relying on coincidences.
Don’t code in the dark. Build an application you don’t fully grasp, or use a technology you don’t understand, and you’ll likely be bitten by coincidences. If you’re not sure why it works, you won’t know why it fails.
Proceed from a plan, whether that plan is in your head, on the back of a cocktail napkin, or on a whiteboard.
Rely only on reliable things. Don’t depend on assumptions. If you can’t tell if something is reliable, assume the worst.
Document your assumptions. Design by Contract, can help clarify your assumptions in your own mind, as well as help communicate them to others.
Don’t just test your code, but test your assumptions as well. Don’t guess; actually try it. Write an assertion to test your assumptions. If your assertion is right, you have improved the documentation in your code. If you discover your assumption is wrong, then count yourself lucky.
Prioritise your effort. Spend time on the important aspects; more than likely, these are the hard parts. If you don’t have fundamentals or infrastructure correct, brilliant bells and whistles will be irrelevant.
Don’t be a slave to history. Don’t let existing code dictate future code. All code can be replaced if it is no longer appropriate. Even within one program, don’t let what you’ve already done constrain what you do next be ready to refactor. This decision may impact the project schedule. The assumption is that the impact will be less than the cost of not making the change.
Code needs to evolve; it’s not a static thing.
You refactor when you’ve learned something; when you understand something better than you did last year, yesterday, or even just ten minutes ago. Perhaps you’ve come across a stumbling block because the code doesn’t quite fit anymore, or you notice two things that should really be merged, or anything else at all strikes you as being “wrong,” don’t hesitate to change it. There’s no time like the present.
Make sure that users of the affected code know that it is scheduled to be rewritten and how this might affect them.
Your users have a stake in the product that you deliver as it affects the way they interact with the software.
One thing I have realised from working at an early-stage startup from day one is that most of the time, your modelling of the real world will be wrong, and that is absolutely okay. In the past two years of working at Commenda, we have discovered many bad assumptions and iterated upon them. The more you question and validate your assumptions, the faster you will be able to iterate.
you need to consider how an external actor could deliberately screw up the system.
The real world is messy, conflicted, and unknown. In that world, exact specifications of anything are rare, if not downright impossible. That’s where we programmers come in. Our job is to help people understand what they want. In fact, that’s probably our most valuable attribute.
I personally maintain a list of features that are outside our product planning roadmap that our operations team would love to have—the little details that make working with our software a more joyful experience for them.
Your role in this is to interpret what the client says and to feed back to them the implications. This is both an intellectual process and a creative one: you’re thinking on your feet and you’re contributing to a solution that is likely to be better than one that either you or the client would have produced alone.
Requirements Are Learned in a Feedback Loop
Since we work very closely with accountants at commenda, the constant feedback loop between them and the engineering team has immensely helped us figuring out the product behaviors that is most useful for our users
There’s a simple technique for getting inside your clients’ heads that isn’t used often enough: become a client. Are you writing a system for the help desk? Spend a couple of days monitoring the phones with an experienced support person. Are you automating a manual stock control system? Work in the warehouse for a week.
The fundamental understanding of how processes work definitely works as a competitive advantage.
Jeff Weinstein here talks about the operational efficiencies of a company. He explains that the product is a combination of software and physical processes, not just software alone.
Every now and again, you will find yourself embroiled in the middle of a project when a really tough puzzle comes up: some piece of engineering that you just can’t get a handle on, or perhaps some bit of code that is turning out to be much harder to write than you thought. Maybe it looks impossible. But is it really as hard as it seems?
You must challenge any preconceived notions and evaluate whether or not they are real, hard-and-fast constraints. It’s not whether you think inside the box or outside the box. The problem lies in finding the box—identifying the real constraints.
You’ll be amazed how often the answer will just pop into your head when you deliberately distract yourself.
I usually prefer to go for walks or read something off topic when I am too much involved with a problem statement, it helps me zoom out for a bit.
Criticise the code, not the person. “Let’s look at this block” sounds much better than “you’re wrong.”
Your team’s culture is defined by your behavior, not your words
There is no single plan you can follow when you develop software. Three of the four values tell you that. They’re all about gathering and responding to feedback. These decisions are always contextual: they depend on who you are, the nature of your team, your application, your tooling, your company, your customer, the outside world; an incredibly large number of factors, some major and some trivial. No fixed, static plan can survive this uncertainty. The feedback loop also applies at the highest level of a project. Some of our most successful work has happened when we started working on a client’s requirements, took a single step, and realised that what we were about to do wasn’t necessary, that the best solution didn’t even involve software.
Great project teams have a distinct personality. People look forward to meetings with them, because they know that they’ll see a well-prepared performance that makes everyone feel good. The documentation they produce is crisp, accurate, and consistent. The team speaks with one voice. They may even have a sense of humour.
The goal of course isn’t to “do Scrum,” “do agile,” “do Lean,” or what-have-you. The goal is to be in a position to deliver working software that gives the users some new capability at a moment’s notice. Not weeks, months, or years from now, but now. For many teams and organisations, continuous delivery feels like a lofty, unattainable goal, especially if you’re saddled with a process that restricts delivery to months, or even weeks. But as with any goal, the key is to keep aiming in the right direction.
Note that being able to deliver on demand does not mean you are forced to deliver every minute of every day. You deliver when the users need it, when it makes business sense to do so.
Once a human tester finds a bug, it should be the last time a human tester finds that bug. The automated tests should be modified to check for that particular bug from then on, every time, with no exceptions, no matter how trivial, and no matter how much the developer complains and says, “Oh, that will never happen again.” Because it will happen again. And we just don’t have the time to go chasing after bugs that the automated tests could have found for us. We have to spend our time writing new code—and new bugs.
Our goal as developers is to delight users. That’s why we’re here. Not to mine them for their data, or count their eyeballs or empty their wallets. Nefarious goals aside, even delivering working software in a timely manner isn’t enough. That alone won’t delight them.
our users are not particularly motivated by code. Instead, they have a business problem that needs solving within the context of their objectives and budget. Their belief is that by working with your team they’ll be able to do this.
Their expectations are not software related. They aren’t even implicit in any specification they give you (because that specification will be incomplete until your team has iterated through it with them several times).
I love the analogy Jeff Weinstein uses here to describe company starting process with Stripe Atlas
I guess today, it will be true that when you go to start a company on Atlas, it will just be a single click. You go to type in your friends names, what the name of the company will be. It'll tell you if it's available automatically or not. You can split the company up 50 50 or whatever you want to do. Fill out a few things and you click go. And then like a burrito coming to you.
I have written something similar to this in the past as well
If you want to delight your client, forge a relationship with them where you can actively help solve their problems. Even though your title might be some variation of “Software Developer” or “Software Engineer,” in truth it should be “Problem Solver.” That’s what we do, and that’s the essence of a Pragmatic Programmer. We solve problems.
Pragmatic Programmers don’t shirk from responsibility. Instead, we rejoice in accepting challenges and in making our expertise well known. If we are responsible for a design, or a piece of code, we do a job we can be proud of.
Artisans of an earlier age were proud to sign their work. You should be, too.
We want to see pride of ownership. “I wrote this, and I stand behind my work.” Your signature should come to be recognized as an indicator of quality. People should see your name on a piece of code and expect it to be solid, well written, tested, and documented. A really professional job. Written by a professional.
Maintainability And Readability | Prime Reacts
You probably don't care enough
From Vim To Zed - The primagen
Personal standards for work[notes]
Abstraction Bad? | Clean Code : Horrible Performance : (Clip) Interview
Building product at Stripe: craft, metrics, and customer obsession | Jeff Weinstein (Product lead)
Your team’s culture is defined by your behavior, not your words