12 Dec 2018

feedPlanet Twisted

Itamar Turner-Trauring: Tests won't make your software correct

Automated tests are immensely useful. Once you've started writing tests and seen their value, the idea of writing software without them becomes unimaginable.

But as with any technique, you need to understand its limitations. When it comes to automated testing-unit tests, BDD, end-to-end tests-it's tempting to think that if your tests pass, your software is correct.

But tests don't, tests can't tell you that your software is correct. Let's see why.

How to write correct software

To implement a feature or bugfix, you go through multiple stages; they might be compressed or elided, but they are always necessary:

  1. Identification: Figure out what the problem is you're trying to solve.
  2. Solution: Come up with a solution.
  3. Specification: Define a specification, the details of how the solution will be implemented.
  4. Implementation: Implement the specification in code.

Your software might end up incorrect at any of these points:

  1. You might identify the wrong problem.
  2. You might choose the wrong solution.
  3. You might create a specification that doesn't match the solution.
  4. You might write code that doesn't match the specification.

Only human judgment can decide correctness

Automated tests are also a form of software, and are just as prone to error. The fact that your automated tests pass doesn't tell you that your software is correct: you may still have identified the wrong problem, or chosen the wrong solution, and so on.

Even when it comes to ensuring your implementation matches your specification, tests can't validate correctness on their own. Consider the following test:

def test_addition():
    assert add(2, 2) == 5

From the code's perspective-the perspective of an automaton with no understanding-the correct answer of 4 is the one that will cause it to fail. But merely by reading that you can tell it's wrong: you, the human, are key.

Correctness is something only a person can decide.

The value of testing: the process

While passing tests can't prove correctness, the process of writing tests and making them pass can help make your software correct. That's because writing the tests involves applying human judgment: What should this test assert? Does match the specification? Does this actually solve our problem?

When you go through the loop of writing tests, writing code, and checking if tests pass, you continuously apply your judgment: is the code wrong? is the test wrong? did I forget a requirement?

You write the test above, and then reread it, and then say "wait that's wrong, 2 + 2 = 4". You fix it, and then maybe you add to your one-off hardcoded tests some additional tests based on core arithmetic principles. Correctness comes from applying the process, not from the artifacts created by the process.

This may seem like pedantry: what does it matter whether the source of correctness is the tests themselves or the process of writing the tests? But it does matter. Understanding that human judgment is the key to correctness can keep you from thinking that passing tests are enough: you also need other forms of applied human judgment, like code review and manual testing.

(Formal methods augment human judgment with automated means… but that's another discussion.)

The value of tests: stability

So if correctness comes from writing the tests, not the tests themselves, why do we keep the tests around?

Because tests ensure stability. once we judge the software is correct, the tests can keep the software from changing, and thus reduce the chances of its becoming incorrect. The tests are never enough, because the world can change even if the software isn't, but stability has its value.

(Stability also has costs if you make the wrong abstraction layer stable…)

Tests are useful, but they're not sufficient

To recap:

  1. Write automated tests.
  2. Run those tests.
  3. Don't mistake passing tests for correctness: you will likely need additional processes and techniques to achieve that.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

12 Dec 2018 5:00am GMT

09 Dec 2018

feedPlanet Twisted

Moshe Zadka: Office Hours

If you want to speak to me, 1-on-1, about anything, I want to be able to help. I am a busy person. I have commitments. But I will make the time to talk to you.

Why?

  • I want to help.
  • I think I'll enjoy it. I like talking to people.

What?

I can offer opinions and experience on programming in general, Python, UNIX, the software industry and other topics.

How did you come up with the idea?

I am indebted to Robert Heaton for the idea and encouragement.

Should I...?

Sure! Especially if you have few connections in the industry, and have questions, I can talk to you. I am a fluent speaker of English and Hebrew, so you do need to be able to converse in one of those...

E-mail me!

09 Dec 2018 5:30am GMT

03 Dec 2018

feedPlanet Twisted

Itamar Turner-Trauring: 'Must be willing to work under pressure' is a warning sign

As a programmer looking for a job, you need to be on the lookout for badly managed companies. Whether it's malicious exploitation or just plain incompetence, the less time you waste applying for these jobs, the better.

Some warning signs are subtle, but not all. One of the most blatant is a simple phrase: "must be willing to work under pressure."

The distance between we and you

Let's take a look at some quotes from real job postings. Can you spot the pattern?

If you look at reviews for these companies, many of them mention long working hours, which is not surprising. But if you read carefully there's more to it than that: it's not just what they're saying, it's also how they're saying it.

When it comes to talking about the company values, for example, it's always in the first person: "we are risk-takers, we are thoughtful and careful, we turn lead into gold with a mere touch of our godlike fingers." But when it comes to pressure it's always in the second person or third person: it's always something you need to deal with.

Who is responsible for the pressure? It's a mysterious mystery of strange mystery.

But of course it's not. Almost always it's the employer who is creating the pressure. So let's switch those job requirements to first person and see how it reads:

That sounds even worse, doesn't it?

Dysfunctional organizations (that won't admit it)

When phrased in the first person, all of these statements indicate a dysfunctional organization. They are doing things badly, and maybe also doing bad things.

But it's not just that they're dysfunctional: it's also that they won't admit it. Thus the use of the second or third person. It's up to you to deal with this crap, cause they certainly aren't going to try to fix things. Either:

  1. Whoever wrote the job posting doesn't realize they're working for a dysfunctional organization.
  2. Or, they don't care.
  3. Or, they can't do anything about it.

None of these are good things. Any of them would be sufficient reason to avoid working for this organization.

Pressure is a choice

Now, I am not saying you shouldn't take a job involving pressure. Consider the United States Digital Service, for example, which tries to fix and improve critical government software systems.

I've heard stories from former USDS employees, and yes, sometimes they do work under a lot of pressure: a critical system affecting thousands or tens of thousands of people goes down, and it has to come back up or else. But when the USDS tries to hire you, they're upfront about what you're getting in to, and why you should do it anyway.

They explain that if you join them your job will be "untangling, rewiring and redesigning critical government services.". Notice how "untangling" admits that some things are a mess, but also indicates that your job will be to make things better, not just to passively endure a messed-up situation.

Truth in advertising

There's no reason why companies couldn't advertise in the some way. I fondly imagine that someone somewhere has written a job posting that goes like this:

"Our project planning is a mess. We need you, a lead developer/project manager who can make things ship on time. We know you'll have to say 'no' sometimes, and we're willing to live with that."

Sadly, I've never actually encountered such an ad in the real world.

Instead you'll be told "you must be able to work under pressure." Which is just another way of saying that you should find some other, better jobs to apply to.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

03 Dec 2018 5:00am GMT

29 Nov 2018

feedPlanet Twisted

Hynek Schlawack: Python Application Dependency Management in 2018

We have more ways to manage dependencies in Python applications than ever. But how do they fare in production? Unfortunately this topic turned out to be quite polarizing and was at the center of a lot of heated debates. This is my attempt at an opinionated review through a DevOps lens.

29 Nov 2018 5:00pm GMT

Moshe Zadka: Common Mistakes about Generational Garbage Collection

(Thanks to Nelson Elhage and Saivickna Raveendran for their feedback on earlier drafts. All mistakes that remain are mine.)

When talking about garbage collection, the notion of "generational collection" comes up. The usual motivation given for generational garbage collection is that "most objects die young". Therefore, we put the objects that survive a collection cycle (and therefore have proven some resistance) in a separate generation that we scan less often.

This is an optimization if the probability of an object that has survived a cycle to be garbage by the time the next collection cycle has come around is lower than the probability of a newly allocated object to be garbage.

In a foundational paper Infant mortality and generational garbage collection, Dr. Baker laid out an argument deceptive in its simplicity.

Dr. Baker asks the question: "Can we model a process where most objects become garbage fast, but generational garbage collection would not improve things?". His answer is: of course. This is exactly the probability distribution of radioactive decay.

If we have a "fast decaying element", say with a half-life of one second, than 50% of the element's atoms decay in one second. However, keeping the atoms that "survived a generation" apart from newly created atoms is unhelpful: all remaining atoms decay with probability of 50%.

We can bring the probability for "young garbage" as high up as we want: a half-life of half a second, a quarter second, or a microsecond. However, that is not going to make generational garbage collection any better than a straightforward mark-and-sweep.

The Poisson distribution, which models radioactive decay, has the property that P(will die in one second) might be high, but P(will die in one second|survived an hour) is exactly the same: the past does not give us information about the future. This is called the "no memory property" of Poisson distribution.

When talking about generational garbage collection, and especially if we are making theoretical arguments about its helpfulness, we need to make arguments about the distribution, not about the averages. In other words, we need to make an argument that some kinds of objects hang around for a long time, while others tend to die quickly.

One way to model it is "objects are bimodal": if we model objects as belonging to a mix of two Gaussian distributions, one with a small average and one with a big average, then the motivation for generational collection is clear: if we tune it right, most objects that survive the first cycle belong to the other distribution, and will survive for a few more cycles.

To summarize: please choose your words carefully. "Young objects are more likely to die" is an accurate motivation, "Most objects die young" is not. This goes doubly if you do understand the subtlety: do not assume the people you are talking with have an accurate model of how garbage works.

As an aside, some languages decided that generational collection is more trouble than it is worth because the objects that "die young" go through a different allocation style. For example, Go has garbage collection, but it tries to allocate objects on the stack if it can guarantee at compile-time they do not "escape". Because of that, the "first generation" is collected at stack popping time.

CPython has generational garbage collection, but it also has a "zeroth generation" of sorts: when functions return, all local variables get a "decref": a decrease in reference count. Those for whom that results in a 0 reference counts, which is often quite a few, get collected immediately.

29 Nov 2018 3:00am GMT

20 Nov 2018

feedPlanet Twisted

Thomas Vander Stichele: Recursive storytelling for kids

Most mornings I take Phoenix to school, as his school is two blocks away from work.

We take the subway to school, having about a half hour window to leave as the school has a half-hour play window before school really starts, which inevitably gets eaten up by collecting all the things, putting on all the clothes, picking the mode of transportation (no, not the stroller; please take the step so we can go fast), and getting out the door.

At the time we make it out, the subway is usually full of people, as are the cars, so we shuffle in and Phoenix searches for a seat, which is not available, but as long as he gets close enough to a pole and a person who looks like they'd be willing to give up a seat once they pay attention, he seems to get his way more often than not. And sometimes, the person next to them also offers up their seat to me. Which is when the fun begins.

Because, like any parent knows these days, as soon as you sit down next to each other, that one question will come:

"Papa, papa, papa… mag ik jouw telefoon?" (Can I have your phone? - Phoenix and I speak Dutch exclusively to each other. Well, I do to him.)

At which point, as a tired parent in the morning, you have a choice - let them have that Instrument of Brain Decay which even Silicon Valley parents don't let their toddlers use, or push yourself to make every single subway ride an engaging and entertaining fun-filled program for the rest of eternity.

Or maybe… there is a middle way. Which is how, every morning, Phoenix and I engage in the same routine. I answer: "Natuurlijk mag jij mijn telefoon… als je éérst een verhaaltje vertelt." (Of course you can have my phone - if you first tell me a story.)

Phoenix furrows his brows, and asks the only logical follow-up question there is - "Welk verhaaltje?" (Which story?)

And I say "Ik wil het verhaaltje horen van het jongetje en zijn vader die met de metro naar school gaan" (I want to hear the story of the little boy and his dad who take the subway to school.)

And he looks at me with big eyes and says, "Dat verhaaltje ken ik niet." (I don't know that story)

And I begin to tell the story:

"Er was eens… een jongetje en zijn vader." (Once upon a time, there was a little boy and his father. Phoenix already knows the first three words of any story.)
"En op een dag… gingen dat jongetje en zijn vader met de metro naar school." (And one day… the little boy and his father took the subway to school. The way he says "op een dag" whenever he pretends to read a story from a book is so endearing it is now part of our family tradition.)

"Maar toen de jongen en zijn vader op de metro stapten zat de metro vol met mensen. En het jongetje wou zitten, maar er was geen plaats. Tot er een vriendelijke mevrouw opstond en haar plaats gaf aan het jongetje, en het jongetje ging zitten. En toen stond de meneer naast de mevrouw ook recht en de papa ging naast het jongetje zitten." (But when the little boy and his father got on the subway, it was full of people. And the little boy wanted to sit but there was no room. Until a friendly woman stood up and gave up her seat to the little boy, so the little boy sat down. And then the man next to the woman also stood up and his father sat down next to him.)

"En toen de jongen op de stoel zat, zei het jongetje, Papa papa papa papa papa papa papa…"(And when the boy sat down on the chair, he said Papa papa papa papa papa papa)

"Ja?, zei papa." (Yes?, said papa.)

"Papa, mag ik jouw telefoon"? (Papa, can I have your phone?)

"Natuurlijk jongen….. als je éérst een verhaaltje vertelt." (Of course son… if you first tell me a story.)

At which point, the story folds in on itself and recurses, and Phoenix's eyes light up as he mouths parts of the sentences he already remembers, and joins me in telling the next level of recursion of the story.

I apologize in advance to all the closing parentheses left dangling like the terrible lisp programmer I've never given myself the chance to be, but making that train ride be phoneless every single time so far is worth it.

Flattr this!

20 Nov 2018 2:19am GMT

12 Nov 2018

feedPlanet Twisted

Itamar Turner-Trauring: Enthusiasts vs. Pragmatists: two types of programmers and how they fail

Do you love programming for its own sake, or do you do for the outcomes it allows? Depending on which describes you best you will face different problems in your career as a software developer.

Enthusiasts code out of love. If you're an enthusiast you'd write software just for fun, but one day you discovered your hobby could also be your career, and now you get paid to do what you love.

Pragmatists may enjoy coding, but they do it for the outcomes. If you're a pragmatist, you write software because it's a good career, or for what it enables you to do and build.

There's nothing inherently good or bad about either, and this is just a simplification. But understanding your own starting point can help you understand and avoid some of the problems you might encounter in your career.

In this post I will cover:

  1. Why many companies prefer to hire enthusiasts.
  2. The career problems facing enthusiasts, and how they can solve them.
  3. The career problems facing pragmatists, and how they can solve them.

Why companies prefer hiring enthusiasts

Before we move on to specific career problems you might face, it's worth looking at the bigger picture: the hiring and work environment.

Many companies prefer to hire enthusiast programmers: from the way they screen candidates to the way they advertise jobs, they try to hire people who care about the technology for its own sake. From an employer's point of view, enthusiasts have a number of advantages:

  1. In a rapidly changing environment, they're more likely to keep up with the latest technologies. Even better, they're more likely to do so in their free time, which means the company can spend less on training.
  2. Since they'd write software for free, it's easier to pay enthusiasts less money.
  3. It's also easier to get enthusiasts to work long hours.
  4. Finally, since enthusiasts care more about the technical challenge than the goals of the product, they're less likely to choose their work based on ethical or moral judgments.

But while many companies prefer enthusiasts, this isn't always in the best interest of either side, as we'll see next.

The career problems facing enthusiasts

So let's say you're an enthusiast. Here are some of the career problems you might face; not everyone will have all these problems, but it's worth paying attention to see if you're suffering from one or more of them.

1. Exploitation

As I alluded to above, companies like enthusiasts because they're worse negotiators.

If you love what you do you'll accept less money, you'll work long hours, and you'll ask less questions. This can cause you problems in the long run:

So even if you code for fun, you should still learn how to negotiate, if only out of self-defense.

2. Being less effective as an employee

Matt Dupree has an excellent writeup about why being an enthusiast can make you a worse worker; I don't want to repeat his well-stated points here. Here are some additional ways in which enthusiasm can make you worse at your job:

3. Work vs. art

Finally, as an enthusiast you might face a constant sense of frustration. As an enthusiast, you want to write software for fun: solve interesting problems, write quality code, fine-tune your work until it's beautiful.

But a work environment is all about outcomes, not about craft. And that means a constant pressure to compromise your artistic standards, a constant need to work on things that aren't fun, and a constant need to finish things on time, rather than when you're ready.

So unless you want to become a pragmatist, you might want to get back more time for yourself, time where you can write code however you like. You could, for example, negotiate a 3-day weekend.

The career problems facing pragmatists

Pragmatists face the opposite set of problems; again, not all pragmatists will have all of these problems, but you should keep your eye out to see if they're affecting you.

1. It's harder to find a job

Since many companies actively seek out enthusiasts, finding a job as a pragmatist can be somewhat harder. Here are some things you can do to work around this:

2. You need to actively keep your skills up

Since you don't care about technology for technology's sake, it can be easy to let your skills get out of date, especially if you work for a company that doesn't invest in training. To avoid this:

3. Pressure to work long hours

Finally, you will often encounter pressure both from management and-indirectly-from enthusiast peers to work long hours. Just remember that working long hours is bad for you and your boss (even if they don't realize it).

Programmer, know thyself

So are you an enthusiast or a pragmatist?

These are not exclusive categories, nor will they stay frozen with time-these days I'm more of a pragmatist, but I used to be more of an enthusiast-but there is a difference in attitudes. And that difference will lead to different choices, and different problems.

Once you know who you are, you can figure out what you want-and avoid the inevitable obstacles along the way.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

12 Nov 2018 5:00am GMT

09 Nov 2018

feedPlanet Twisted

Itamar Turner-Trauring: The cold incubator: the VC dream of workerless wealth

Chicken incubators are hot: eggs need heat to thrive. This was a different sort of incubator, a glass enclosure within a VC office. The VCs had the best views, but even we could look down ten stories and see the sun sparkling on the Charles River and reflecting off the buildings of downtown Boston.

It was summer when I joined the startup, but even so the incubator was freezing. The thermostat was skewed by a hot light sensor right below it, and the controls reset every night, so mornings were frigid. I took to wearing sweaters and fingerless gloves; the team at the other side of the incubator had figured out a way cover the AC vents with cardboard in a way that wasn't visible to passersby.

But I didn't have to suffer from the cold for very long. Soon after I joined the startup I unwittingly helped trigger our eviction from the rent-free Eden of the incubator to the harsher world outside.

The fall from grace

Most of the people who worked out of the incubator just used a laptop, or perhaps a monitor. But I like working with a standing desk, with a large Kinesis keyboard, and an external monitor on top.

My desk towered over everyone else: it was big and imposing and not very neat. Which is to say, the incubator started looking like a real office, not a room full of identical tables with a few Macbook Airs scattered hither and yon. And standing under the freezing air conditioner vent made my arms hurt, so I had to setup my desk in a way that was visible from the outside of the incubator.

And that view was too much for one of the partners in the VC firm. There were too many of us, we had too many cardboard boxes, my standing desk was just too big: it was time for us to leave the incubator.

The dream of workerless wealth

VCs take money, and (if all goes well) turn it into more money. But the actual reality of the work involved was too unpleasantly messy, too uncouth to be exposed to the sensibilities of wealthy clients.

We had to be banished out of sight, the ever-so-slightly grubby realities of office work hidden away, leaving only the clean cold dream of capital compounding through the genius of canny investors.

This dream-a dream of profit without workers-is the driving force behind many an investment fad:

And if you work for a VC-funded startup, this dream takes on a nightmarish tinge when it turns to consider you.

Unbanished-for now

The point here is not that VCs want to reduce effort: who wouldn't want a more efficient world? The dream is not driven by visions of efficiency, it's about status and aesthetics: doing actual work is ugly, and paying for work is offensive.

Of course, some level of work is always necessary and unavoidable. And so VC firms understand that the startups they fund must hire workers like me and you.

But the cold dream is always there, whispering in the background: these workers take equity, they take cash, they're grubby. So when times are good hiring has to be done as quickly as possible, but when times are bad the layoffs come just as fast.

And when you are working, you need to work as many hours as humanly possible, not because it's efficient-it isn't-but because paying for your time is offensive, and so you better damn well work hard. Your work may be necessary, but to the cold dream it's a necessary-and ugly-evil.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

09 Nov 2018 5:00am GMT

07 Nov 2018

feedPlanet Twisted

Moshe Zadka: The Conference That Was Almost Called "Pythaluma"

As my friend Thursday said in her excellent talk (sadly, not up as of this time) naming things is important. Avoiding in-jokes is, in general, a good idea.

It is with mixed feelings, therefore, that my pun-loving heart reacted to Chris's disclosure that the most common suggestion was to call the conference "Pythaluma". However, he decided to go with the straightforward legible name, "North Bay Python".

North of the city by the bay, lies the quiet yet chic city of Petaluma, where North Bay Python takes place. In a gold-rush-city turned sleepy wine country, a historical cinema turned live show venu hosted Python enthusiasts in a single-track conference.

Mariatta opened the conference with her gut-wrenching talk about being a core Python developer. "Open source sustainability" might be abstract words, but it is easy to forget that for a language that's somewhere between the first and fifth most important (depending on a metric) there are less than a hundred people supporting its core -- and if they stop, the world breaks.

R0ml opened the second day of the conference talking about how:

Talks are still being uploaded to the YouTube channel, and I have already had our engineering team at work watch Hayley's post-mortem of Jurassic Park.

If you missed all of it, I have two pieces of advice:

If you went there, I hope you told me hi. Either way, please say hi next year!

07 Nov 2018 8:00am GMT

02 Nov 2018

feedPlanet Twisted

Itamar Turner-Trauring: When and why to clean up your code: now, later, never

You've got to meet your deadlines, you've got to fix the bug, you've got to ship the product.

But you've also got to think about the future: every bug you introduce now will have to be fixed later, using up even more time. And all those deprecated APIs, out-of-date dependencies, and old ways of doing things really shouldn't be there.

So when do you clean up your code?

Do you do it now?

Later?

Never?

In this article I'll go over a set of heuristics that will help you decide when to apply three kinds of fixes:

  1. Updating dependencies that are out-of-date and usages of deprecated APIs.
  2. Refactoring to fix bad abstractions.
  3. Miscellaneous Cleanups of anything else, from coding standard violations to awkward idioms.

Heuristics by situation

Prototyping

Before you start building something in earnest, you might start with a prototype (or what Extreme Programming calls a "spike"). You're not going to keep this code, you're just exploring the problem and solution space to see what you can learn.

Given you're going to throw away this code, there's not much point in Updating or Miscellaneous Cleanups. And if you're just trying to understand an existing API or technical issue, you won't be doing much Refactoring wither.

On the other hand, if your goal with prototyping is to find the right abstraction, you will be doing lots of Refactoring.

  1. Updating: never.
  2. Refactoring: now if you're trying to prototype an API or abstraction, otherwise never.
  3. Miscellaneous Cleanups: never.

A new project

When you're starting a completely new project, the decisions you make will have a strong impact on the maintenance code going forward.

This is a great opportunity to start with the latest (working) dependencies, situation-specific best practices and maintainable code, and the best abstractions you can come up with. You probably won't get them completely right, but it's usually worth spending the time to try to get it as close as possible.

  1. Updating: now.
  2. Refactoring: now.
  3. Miscellaneous Cleanups: now.

An emergency bugfix

You need to get a bug fix to users ASAP. While you might see problems along the way, but unless they're relevant to this particular bug fix, it's best to put them off until later.

Sometimes that might mean fixing the bug twice: once in a quick hacky way, and a second time after you've done the necessary cleanups.

  1. Updating: later.
  2. Refactoring: later.
  3. Miscellaneous Cleanups: later.

New feature or non-urgent bugfix

When you have a project in active development and you're doing ongoing work, whether features or bug fixes, you have a great opportunity to incrementally clean up your code.

You don't need to fix everything every time you touch the code. Instead, an ongoing cleanup of code you're already touching will cumulatively keep your codebase in good shape. See Ron Jefferies' excellent article for details.

  1. Updating: now, for code you're touching.
  2. Refactoring: now, for code you're touching.
  3. Miscellaneous Cleanups: now, for code you're touching.

A project in maintenance mode

Eventually your project will be finished: not much new development is done, and mostly it just gets slightly tweaked every few months when something breaks or a drop-down menu needs an extra option.

Your goal at this point is to do the minimum work necessary to keep the project going. Refactoring and Miscellaneous Cleanups aren't necessary, but Updates might be-dependencies can stop working, or need security updates. And jumping your dependencies 5 years ahead is often much harder than incrementally doing 5 dependency updates at yearly intervals.

So whenever you have to do fix a bug, you should update the dependencies-ideally to Long Term Support releases to reduce the need for API usage updates.

  1. Updating: now, ideally to Long Term Support releases.
  2. Refactoring: never.
  3. Miscellaneous Cleanups: never.

Balancing present and future

Software projects tend to ongoing processes, not one-off efforts. A cleanup now might save you time later-but if you have a deadline to meet now, it might be better to put it off even at the cost of slightly more work later on.

So takes this article only a starting point: as with any heuristic, there will be exceptions to the rule, and you need to be guided by your situation and your goals.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

02 Nov 2018 4:00am GMT

24 Oct 2018

feedPlanet Twisted

Hynek Schlawack: Testing & Packaging

How to ensure that your tests run code that you think they are running, and how to measure your coverage over multiple tox runs (in parallel!).

24 Oct 2018 8:00am GMT

Itamar Turner-Trauring: No more crunch time: the better way to meet your deadlines

Deadlines are hard to navigate.

On the one hand you risk crashing into the rocks of late delivery, and on the other you risk drowning in a whirlpool of broken software and technical debt.

And if you end up working long hours-if your only solution is crunch time-you risk both burnout and technical debt at the same time. Metaphorically, you risk drowning in a whirlpool full of fire-breathing rocks.

Given too much work and not enough time, you need to say "no" to your boss and negotiate a better set of deliverables-but what exactly should you be aiming for?

This dilemma is a false dichotomy. Quality is situation-specific and feature-specific, and rote answers aren't enough to decide on an implementation strategy.

The real answer is to prioritize the work that really matters in your particular situation, and jettison the rest until after the deadline. And that means saying "no": saying no to your boss, saying no to your customer, and saying no to yourself.

No, all the code can't be perfect (but this part needs to be rock solid).

No, that feature isn't important for addressing our goals.

No, we can't just add one small thing. But yes, we will do what really matters.

Let's consider two examples: the same task, but very different goals and implementations.

Deadline #1: Raise money or lose your job

I once worked at a startup that was starting to run out of money: our existing business was not working. Luckily, the co-founders had come up with a plan. We would pivot to a trendy new area-Docker was just gaining traction-where we could build something no one else could do at the time.

Now, this meant were going to be building a distributed system, a notoriously difficult and complex task. And we had a deadline we had to meet: we were going to do a press campaign for our initial release, and that requires a few weeks of lead time. And of course on a broader level we needed to make enough of an impression that we could get funding before our current money ran out.

How do you build a complex, sophisticated piece of software with a short deadline? You start with your goals.

Start with your goals

Our goal was to demonstrate the key feature that only our software could do. We decided to do that by having users walk through a tutorial that demonstrated our value proposition. If the user was impressed with our tutorial then we'd succeeded; we explicitly told users that the software was not yet usable in the real world.

Based on this operational goal we were able to make a whole slew of simplifications:

  1. A production system would need to support parallel operation, but our tutorial only needed to be used by a single user. So we implemented the distributed system by having a CLI that SSHed into a machine and ran another CLI.
  2. A production system would need to handle errors, but our tutorial could run in a controlled environment. We created a Vagrant config that started two virtual machines, and didn't bother writing error handling for lost connections, inaccessible machines, and so on.
  3. A production system would need to be upgradeable and maintainable, but our tutorial would only be run once. So we didn't bother writing unit tests for most of it, and focused instead on manually running through the tutorial.

Now, you might be thinking "but you're choosing to abandon quality". But again, none of the things we dropped were relevant to our goal at that time. Spending time on "best practices" that aren't relevant to your particular situation is a waste of time.

We were very clear in all our documentation and marketing efforts that this was a demonstration only, and that we would be following up later with a production-ready release.

Prioritize features by goals

Even with these simplifications, we ended up dropping features to meet the deadline. How did we decide which futures to drop?

We started by implementing the minimal features needed to demonstrate our core value, and so when we ran out of time we dropped the remaining, less critical features. We stopped coding before the drop date (a week or a few days, I believe), and focused just on testing and polishing our documentation.

Dropping features was quite alright: the idea was good enough, the tutorial was compelling enough, and our VP of Marketing was skilled enough, that we were able to raise a $12 million Series A based off that unscalable, unmaintainable piece of software. And after the initial release and publicity push we had time to implement those later features to keep up our momentum.

Deadline #2: Production-ready software

Once we had VC funding we rebuilt the product with the same set of features, but a very different goal: a production-ready product. That meant we needed a good architecture, an installer, error handling, good test coverage, and so on. It took much longer, and required a much bigger team, but that was OK: we had a different goal, and enough funding to allow for a longer deadline (again, based on publicity needs).

Even so, we made sure to choose and prioritize our work based on our goal: getting users and customers for our product. Our initial prototype had involved a peer-to-peer storage transfer mechanism, and making that production ready would have been a large R&D exercise. So in the short term we focused on cloud storage, a much simpler problem.

And we made sure to drop less important features as deadlines approached. We certainly didn't do a perfect job, e.g. we dropped one feature that was half-implemented. We would have done better not starting it at all, since it was less important. But, we succeeded: the code was maintainable, people started using it, and we didn't have to rely on crunch time to deliver.

Beyond universal solutions

There is no universal solution that will work everywhere, no easy answers. All you can do is ask "why": why are we doing this, and why does this matter?

Once you know your goals, you can try and prioritize-and negotiate for-a solution that achieves your goals, meets the deadline, and doesn't involve long hours:

  1. Drop features that don't further your goals, and start with the most important features, in case you run out of time for the rest.
  2. Write high-quality code in the places where it matters, and drop "quality" where it doesn't (what Havoc Pennington calls "professional corner-cutting", emphasis on professional.)
  3. And if you still have too much work, it's time to have a discussion with your boss or customer about what tradeoffs they actually want.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

24 Oct 2018 4:00am GMT

16 Oct 2018

feedPlanet Twisted

Jonathan Lange: Notes on test coverage

These are a few quick notes to self, rather than a cogent thesis. I want to get this out while it's still fresh, and I want to lower my own mental barrier to publishing here.

I've been thinking about test coverage recently, inspired by conversations that followed DRMacIver's recent post.

Here's my current working hypothesis:

The justification is that "the test of all knowledge is experiment" [0]. While we should absolutely make our code easy to reason about, and prove as much as we can about it, we need to check what it does against actual reality.

Simple testing really can prevent most critical failures. It's OK to not test some part of your code, but that should be a conscious, local, recorded decision. You have to explicitly opt out of test coverage. The tooling should create a moment where you either write a test, or you turn around and say "hold my beer".

Switching to this for an existing project can be prohibitively expensive, though, so a ratchet is a good idea. The ratchet should be "lines of uncovered code", and that should only be allowed to go down. Don't ratchet on percentages, as that will let people add new lines of uncovered code.

Naturally, all of this has to be enforced in CI. No one is going to remember to run the coverage tool, and no one is going to remember to check for it during code review. Also, it's almost always easier to get negative feedback from a robot than a human.

I tagged this post with Haskell, because I think all of this is theoretically possible to achieve on a Haskell project, but requires way too much tooling to set up.

As a bit of an experiment, I set up a test coverage ratchet with graphql-api. I wanted both to test out my new enthusiasm for aiming for 100% coverage, and I wanted to make it easier to review PRs.

The ratchet script is some ad hoc Python, but it's working. External contributors are actually writing tests, because the computer tells them to do so. I need to think less hard about PRs, because I can look at the tests to see what they actually do. And we are slowly improving our test coverage.

I want to build on this tooling to provide something genuinely good, but I honestly don't have the budget for it at present. I hope to at least write a good README or user guide that illustrates what I'm aiming for. Don't hold your breath.

[0] The Feynman Lectures on Physics, Richard Feynman

16 Oct 2018 11:00pm GMT

10 Oct 2018

feedPlanet Twisted

Itamar Turner-Trauring: The next career step for Senior Software Engineers (that isn't management)

You've been working as a programmer for a few years, you've been promoted once or twice, and now you're wondering what's next. The path until this point was straightforward: you learned how to work on your own, and then you get promoted to Senior Software Engineer or some equivalent job title.

But now there's no clear path ahead.

Do you become a manager and stop coding?

Do you just learn new technologies, or is that not enough?

What should you be aiming for?

In this post I'd like to present an alternative career progression, an alternative that will give you more autonomy, and more bargaining power. And unlike becoming a manager, it will still allow you to write code.

From coding to solving problems

In the end, your job as a programmer is solving problems, not writing code. Solving problems requires:

  1. Finding and identifying the problem.
  2. Coming up with a solution.
  3. Implementing the solution.

Each of these can be thought of a skill-tree: a set of related skills that can be developed separately and in parallel. In practice, however, you'll often start in reverse order with the third skill tree, and add the others on one by one as you become more experienced.

Randall Koutnik describes these as job titles of a sort, a career progression: Implementers, Solvers, and Finders.

As an Implementer, you're an inexperienced programmer, and your tasks are defined by someone else: you just implement small, well-specified chunks of code.

Let's imagine you work for a company building a website for animal owners. You go to work and get handed a task: "Add a drop-down menu over here listing all iguana diseases, which you can get from the IGUANA_DISEASE table. Selecting a menu item should redirect you the appropriate page."

You don't know why a user is going to be listing iguana diseases, and you don't have to spend too much time figuring out how to implement it. You just do what you're told.

As you become more experienced, you become a Solver: are able to come up with solutions to less well-defined problems.

You get handed a problem: "We need to add a section to the website where pet owners can figure out if their pet is sick." You figure out what data you have and which APIs you can use, you come up with a UI together with the designer, and then you create an implementation plan. Then you start coding.

Eventually you become a Finder: you begin identifying problems on your own and figuring out their underlying causes.

You go talk to your manager about the iguanas: almost no one owns iguanas, why are they being given equal space on the screen as cats and dogs? Not to mention that writing iguana-specific code seems like a waste of time, shouldn't you be writing generic code that will work for all animals?

After some discussion you figure out that the website architecture, business logic, and design are going to have to be redone so that you don't have to write new code every time a new animal is added. If you come up with the right architecture, adding a new animal will take just an hour's work, so the company can serve many niche animal markets at low cost. Designing and implementing the solution will likely be enough work that you're going to have to work with the whole team to do it.

The benefits of being a Finder

Many programmers end up as Solvers and don't quite know what to do next. If management isn't your thing, becoming a Finder is a great next step, for two reasons: autonomy and productivity.

Koutnik's main point is that each of these three stages gives you more autonomy. As an Implementer you have very little autonomy, as a Solver you have more, and as a Finder you have lots: you're given a pile of vague goals and constraints and it's up to you to figure out what to do. And this can be a lot of fun.

But there's another benefit: as you move from Implementer to Solver to Finder you become more productive, because you're doing less unnecessary work.

The better you are at diagnosing and identifying underlying problems, coming up with solutions, and working with others, the less unnecessary work you'll do, and the more productive you'll be.

Leveraging your productivity

If you're a Finder you're vastly more productive, which makes you a far more valuable employee. You're the person who finds the expensive problems, who identifies the roadblocks no one knew where there, who discovers what your customers really wanted.

And that means you have far more negotiating leverage:

So if you want to keep coding, and you still want to progress in your career, start looking for problems. If you pay attention, you'll find them everywhere.



It's Friday afternoon. You just can't write another line of code-but you're still stuck at the office...

What if every weekend could be a 3-day weekend?

10 Oct 2018 4:00am GMT

06 Oct 2018

feedPlanet Twisted

Moshe Zadka: Why No Dry Run?

(Thanks to Brian for his feedback. All mistakes and omissions that remain are mine.)

Some commands have a --dry-run option, which simulates running the command but without taking effect. Sometimes the option exists for speed reasons: just pretending to do something is faster than doing it. However, more often this is because doing it can cause big, possibly detrimental, effects, and it is nice to be able to see what would happen before running the script.

For example, ansible-playbook has the --check option, which will not actually have any effect: it will just report what ansible would have done. This is useful when editing a playbook or changing the configuration.

However, this is the worst possible default. If we have already decided that our command can cause much harm, and one way to mitigate the harm is to run it in a "dry run" mode and have a human check that this makes sense, why is "cause damage" the default?

As someone in SRE/DevOps jobs, many of the utilities I run can cause great harm without care. They are built to destroy whole environments in one go, or to upgrade several services, or to clean out unneeded data. Running it against the wrong database, or against the wrong environment, can wreak all kinds of havoc: from disabling a whole team for a day to actual financial harm to the company.

For this reason, the default of every tool I write is to run in dry run mode, and when wanting to actually have effect, explicitly specify --no-dry-run. This means that my finger accidentally slipping on the enter key just causes something to appear on my screen. After I am satisfied with the command, I up-arrow and add --no-dry-run to the end.

I now do it as a matter of course, even for cases where the stakes are lower. For example, the utility that publishes this blog has a --no-dry-run that publishes the blog. When run without arguments, it renders the blog locally so I can check it for errors.

So I really have no excuses... When I write a tool for serious production system, I always implement a --no-dry-run option, and have dry runs by default. What about you?

06 Oct 2018 7:00am GMT

02 Oct 2018

feedPlanet Twisted

Moshe Zadka: Why No Dry Run?

(Thanks to Brian for his feedback. All mistakes and omissions that remain are mine.)

Some commands have a --dry-run option, which simulates running the command but without taking effect. Sometimes the option exists for speed reasons: just pretending to do something is faster than doing it. However, more often this is because doing it can cause big, possibly detrimental, effects, and it is nice to be able to see what would happen before running the script.

For example, ansible-playbook has the --check option, which will not actually have any effect: it will just report what ansible would have done. This is useful when editing a playbook or changing the configuration.

However, this is the worst possible default. If we have already decided that our command can cause much harm, and one way to mitigate the harm is to run it in a "dry run" mode and have a human check that this makes sense, why is "cause damage" the default?

As someone in SRE/DevOps jobs, many of the utilities I run can cause great harm without care. They are built to destroy whole environments in one go, or to upgrade several services, or to clean out unneeded data. Running it against the wrong database, or against the wrong environment, can wreak all kinds of havoc: from disabling a whole team for a day to actual financial harm to the company.

For this reason, the default of every tool I write is to run in dry run mode, and when wanting to actually have effect, explicitly specify --no-dry-run. This means that my finger accidentally slipping on the enter key just causes something to appear on my screen. After I am satisfied with the command, I up-arrow and add --no-dry-run to the end.

I now do it as a matter of course, even for cases where the stakes are lower. For example, the utility that publishes this blog has a --no-dry-run that publishes the blog. When run without arguments, it renders the blog locally so I can check it for errors.

So I really have no excuses... When I write a tool for serious production system, I always implement a --no-dry-run option, and have dry runs by default. What about you?

02 Oct 2018 7:00am GMT