Intro

"Things I Learnt The Hard Way (In 30 Years of Software Development)" started as a simple sequence of toots (the same as "tweets", on Mastodon when I was thinking about a new presentation I could do.

But why "a new presentation"?

I go around my state with a group called "Tchelinux": We usually go to universities and talk to people starting uni, explaining things about free/libre software and sometimes telling people about things they wouldn't normally see in the uni curriculum.

One thing that annoys me is that there are very few presentations about "when things go wrong". All the presentations show prototypes or tell the good stuff, and hide all the wrong things that could happen1. Obviously, after working 30 years in the field of software development, I saw my fair share of things going wrong -- sometimes in unimaginable piles of crap -- and I thought "maybe that's something people would like to hear".

(And, to be completely honest, some of those piles of crap were my own fault.)

And that's when the toot sequence started. Just before I noticed, I spent the whole day just posting this kind of stuff (fortunately, my pile of things in the "incoming" folder was a bit empty at the time) and it had 30 points, plus addenda and a few explanation points. That's when I decided to group all them in a single post.

(Actually, I'm lying: Someone mentioned on Functional Café that I should make a blog post for making it easier to read.)

All I thought when I grouped everything in a post was "this will make things easier for the people following the thread on Mastodon". But then the post appeared on Reddit. And Twitter. And HackerNews. And YCombinator. And none of those where mine.

But here is the thing: Each point was limited by the toot size, which is 500 characters. Sometimes that's not enough to expand the point, explain it properly and add some examples.

And that's how the idea to write this "book" came to life.

One thing you must keep in mind here: These are my options. I understand that not everything is so black and white as put here, and some people's experiences may not match things here. Also, you get a bit cynical about technology after 30 years. So... thread carefully, 'cause here be dragons.

1

Yup, I'm guilty of that too.

Disclaimer

There is one magical thing you need to know when reading this book: It's all personal opinion

A lot of stuff I'm going to discuss throughout this book will come directly from my personal experience in several projects -- system applications, web backend, embedded, mobile, stream processing -- in several different languages -- C, C++, Python, Java, Clojure, Rust. And, because it comes from personal experience, everything reflects my own personal opinion on several subjects.

Obviously, you don't need to agree with every single point. But I hope at least it will make you rethink a few subjects.

Also, sometimes I may mention some examples that people who know me -- either worked with me, heard me complain about some project, inherit one of my projects, I inherit one of the their projects -- may recognized and think I'm attacking the author.

I am not.

We do mistakes. Sometimes we don't know the topic with are attacking, sometimes we don't have full specs, sometimes we don't have the time to write things properly in a crunchtime. And that's why some things don't look as pretty as they should. Heck, if you think I'm attacking the original author of some example, look back the stuff I wrote and you'll see things a lot worse.

But I need the example. I have this hope that showing people a few mistakes can make things better. I want to show people how my opinion built over some subject. And, again, I'm in no way attacking the original author of the code. I may even call the code "stupid", but I'm not calling the author stupid.

With that in mind...

Programming Tips

I'm a software developer. I write code. So here are some of things I learnt about the craft.

Before You Start Writing Code

Before you sit in front of your computer and open your text editor/IDE to write code, maybe you should take a step back and come with some stuff.

Spec First, Then Code

"Without requirements or design, programming is the art of adding bugs to an empty text file." -- Louis Srygley

If you don't know what you're trying to solve, you don't know what to code.

A lot of times we have this feeling of "let me jump straight to the code". But without understanding what problem you're trying to solve, you'd end up writing a bunch of things that doesn't solve anything -- or, at least, anything that should be solved.

So here is the point: Try to get a small spec on whatever you want to solve. But be aware that even that spec may have to be thrown out, as the understanding of the problem tend to grow as long as the project continue.

Yes, it's paradoxical: You need a spec to know what to code to avoid coding the wrong solution, but the spec may be wrong, so you end up solving the wrong solution anyway. So what's the point? The point is, the spec reflects the understanding of a problem at a certain point: All you (and your team) know is there.

The times I stood longer looking at my own code wondering what to do next were when we didn't have the next step defined: It was missing some point of the solution or we didn't have the communication structures defined or something of sorts. Usually, when that happened, I stumbled upon Twitter or Mastodon instead of trying to solve the problem. So when you see yourself doing this kind of stuff -- "I don't know what to do next, and I'm not sure if I'm done with the current problem" -- then maybe it's time to stop and talk to other people in the project to figure that out.

Another way to think this: Erik Deitrich have a post about Don’t Learn to Code — Learn to Automate, something I can get behind 'cause most of us, when doing stuff, think "I need to do this, then I pick that thingy and put it there and from there I do this other work". Basically, we create mental models of specs, step by step, on what we need to do. And, from there, it may be even simpler, 'cause now all you need to learn is "First, how I do this; Ok, got it, now I get the result from this and put there" and so on. You can even have a learning path, if you're a beginner.

Write Steps as Comments

Don't know how to solve your problem? Write the steps as comments in your code.

There you are, looking at the blank file wondering how you're going to solve that problem. Here is a tip:

Take the spec you (or someone else) wrote. Break each point into a series of steps to reach the expected behaviour. You can even write on your natural language, if you don't speak English.

Then fill the spaces between the comments with code.

For example, if you have a spec of "connect to server X and retrieve everything there. Save the content in the database. Remember that server X API allow you can pass an ID (the last ID seen) and you can use it to not retrieve the same content again." Pretty simple, right?

Writing this as comments, pointing the steps you need to make, you may end up with something like this:

// connect to server X
// retrieve posts
// send posts to the database

Ah, you forgot the part about the ID. No problem, you just have to add it in the proper places -- for example, it doesn't make sense to connect to the server before you have the last seen ID:

// open configuration file
// get value of the last seen ID; if it doesn't exist, it's empty.
// connect to server X
// retrieve posts starting at the last seen ID
// send posts to the database
// save the last seen ID in the configuration file

Now it is "easy"1: You just add the code after each comment.

A better option is to change the comments into functions and, instead of writing the code between the comments, you write the functionality in the function themselves and keep a clean view of what your application does in the main code.

1

Yes, that was sarcastic.

Gherkin Is Your Friend to Understand Expectations

Gherkin is file format for writing behaviour tests (BDD). But it can also give you some insights on what you should do.

Alright, let's talk a bit about Gherkin:

Gherkin is a file format created for Cucumber, which describes scenarios, what's in them, what actions the user/system will do and what's expected after those actions, in a very high level, allowing people without programming experience can describe what's expected from the system.

Although Gherkin was born with Cucumber, it is now supported by a bunch of programming languages, through external libraries.

A typical Gherkin file may look something like this:

  • Given that initial system environment
  • When action performed by the user or some external system
  • Then expected system environment

Or, in a more concrete example:

  • Given that The system is retrieving all tweets liked by the user
  • When It finds a tweet with an attachment
  • Then The attachment should be saved along the tweet text

Pretty simple, right?

Now, why I'm mentioning this?

Sometimes, specs are not the most clear source of information about what it is expected from the system, and up can't think of steps to do so. If you're confused about what you should write, asking the person responsible for the request to write something like Gherkin may give you some better insights about it.

Obviously, it won't be complete. People tend to forget the error situations -- like filling the name field with numbers, using characters in age fields, tweets with no text and just attachments -- but at least with a Gherkin description of the system, you can get a better picture of the whole.

Also, you may not like to write specs. That's alright, you can replace them with Gherkin anyway.

Design Patterns Are Used to Name Solution, Not Find Them

Most of the times I saw design patterns being applied, they were applied as a way to find a solution, so you end up twisting a solution -- and, sometimes, the problem it self -- to fit the pattern.

My guess is that the heavy use of "let's apply this design pattern" before even understanding the problem -- or even trying to solve it -- comes as a form of cargo cult: "We saw that people used this pattern and solved their problem, so let's use it too and it will solve our problem". Or, worse: "Design pattern is described by Famous Person, so we must use it".

Here is the thing: Design pattern should not be used as a way to find solution to any problems. You may use some of them as base for your solution, but you must focus on the problem, not the pattern.

"Do a visitor pattern will solve this?" is the wrong question. "What should we do to solve our problem?" is the real question. Once you went there and solved the problem you may look back and see if it is a visitor pattern -- or whatever pattern. If it doesn't, that's alright, 'cause you solved the problem. If it did... well, congratulations, you now know how to name your solution.

I've seen this happening a lot: People have a problem; people decided to use a pattern; the pattern doesn't actually solve the problem (not in the 100% mark, but above 50%); what happens then is that people start twisting the problem to fit the pattern or, worse, add new layers to transform the problem into the pattern.

Thinking Data Flow Beats Patterns

When you're trying to find a solution to your problem, think on the way the data will flow through your code.

Instead of focusing on design patterns, a better way is to think the way the data will flow -- and be transformed -- on your code.

For example, the user will input a number. You'll get this number and find the respective record on the database. This is a transformation -- no, it's not "I'll get the number and receive a complete different thing based upon it", you're actually transforming the number into a record, using the database as a transformation.

(Yes, I know, it's not that clear at the first glance, but you have to think that they are the same data with different representations.)

Most of the time I did that, I managed to come with more clear design for my applications. I didn't even think about how many functions/classes it would be needed to do these kind of transformations, that was something I came up after I could see the data flow.

In a way, this way of thinking gets things more clear 'cause you have a list of steps of transformations you need to do, so you can write them one after another, which prevents a lot of bad code in the future.

The Magic Number Seven, Plus Or Minus Two

"The magical number" is a psychology article about the number of things one can keep in their mind at the same time.

I've seen twice this weird construction on where a function would do some processing, but its return value was the return of this processing, plus the result of a second function and some bit of processing. Nothing major. But the second function would also do some processing and call a third function. And the third function would call a fourth. And the fourth a fifth. And the fifth, a sixth function.

And the "processing" was not something small, like "add two" or "multiply by itself or a configurable value".

Something like this

func_1
 +-- func_2
     +-- func_3
	     +-- func_4
		     +-- func_5
			     +-- func6

(If you need the real processing I saw, it was a class that had a function with some processing and then it would call a function in an injected dependency -- which is pretty nice and dandy. But the injected dependency had an injected dependency, and the third injected dependency also had an injected dependency, and so forth).

Now, when you're trying to understand this kind of code to find a problem, you'll have to keep in mind what the first, second, third, fourth, fifth and sixth functions do, 'cause they are all calling each other (inside them).

This causes some serious mental overflow that shouldn't be necessary.

Not only that, but imagine that you put a log before and after func_1: The log before points the data that's being send to func_1, and the log after its result.

So you'd end up with the impression that func_1 does a lot of stuff, when it actually is passing the transformation along.

(I got a weird experience with a function called expand, which logging before the call would show some raw, compressed data, but the after was not the expanded data, but actually a list of already processed data from the compressed data.)

What would be a better solution, you may ask?

Well, if instead of making func_1 call func_2, you can make it return the result (which may not be the final result, anyway) and then call func_2 with that result.

Something like:

result1 = func_1
result2 = func_2(result1)
result3 = func_3(result2)
result4 = func_4(result3)
result5 = func_5(result4)
result6 = func_6(result5)
result7 = func_7(result6)

(If we go back to the dependency injection chain, you may think that instead of making DI7 receive DI6 as dependency [which would receive DI5 as dependency, which would receive DI4 as dependency, which would receive DI3 as dependency and so forth] you would actually create all the DI (dependency injections) in one single pass and then the starting function would call the dependencies in a single shot, not chaining them.)

Now you can see exactly how the data is being transformed -- and, obviously, the functions would have better names, like expand, break_lines, name_fields and so on, so you can see that that compressed data I mentioned before is actually being decompressed, the content is being broke line by line, the lines are getting names in its fields and so on (and one could even claim that it would make things clear if there was a function after break_lines which would just break_fields, which would make name_fields more obvious -- and in a construction like this it would be almost trivial to add this additional step).

"But that isn't performant!" someone may cry. Well, maybe it's just a bit less performant than the original chained-calls ('cause it wouldn't create and destroy frames in the stack, it would just pile them up and then "unstack" them all in the end), but heck, optimization is for compilers, not people. Your job is to make the code readable and understandable. If you need performance, you can think of a better sequence of steps, not some "let's make this a mess to read" solution.

Just a quick note: Although the famous paper mentions that the number is around 7, new research is actually pointing that the number is way lower than that, at 4. So simply making func_1 call func_2, which would call func_3, which would call func_4 may be enough to overload someone and make them lose the track of what the code does.

Cognitive Cost Is The Readability Killer

"Cognitive dissonance" is a fancy way of saying "I need to remember two (or more) different and contradicting things at the same time to understand this." Keeping those different things in your head creates a cost and it keeps accumulating the more indirect the things are ('cause you'll have to keep all those in your head).

(Disclaimer: I like to use the expression "cognitive dissonance" to make me sound smarter. I usually explain what it means, though.)

To give you an example of a (very mild) cognitive cost, I'll show you this:

  • You have a function called sum(). It does the sum of the numbers of a list.
  • You have another function, called is_pred(). It gets a value and, if it fits the predicate -- a test, basically -- returns True; otherwise, returns False.

So, pretty simple, right? One function sums numbers and another returns a boolean.

Now, what would you say if I shown you this, in Python:

sum(is_pred(x) for x in my_list)

Wait, didn't I say that sum() sums numbers? And that is_pred() returns a boolean. How can I sum booleans? What's the expected result of True + True + False?

Sadly, this works. Because someone, long time ago, didn't think booleans were worth a thing and used an integer instead. And everyone else since then did the same stupid mistake.

But, for you, you'll now read a line that says "summing a boolean list returns a number". And that's two different, disparate things that you suddenly have to keep in mind when reading that line.

That's why types are important. Also, this may sound a bit like the magical number seven, 'cause you have to keep two things at your mind at the same thing but, although that's not near seven, they are not the same, with opposite (for weird meanings of "opposite", in this case) meanings.

Learn The Basics of Functional Programming

At this point, you should at least have heard about how cool functional programming is. There are a lot of concepts here, but at least the very basic ones you should keep in mind.

A lot of talks about functional programming come with weird words like "functors" and "monads". It doesn't hurt to know what they really mean (disclaimer: I still don't). But some other stuff coming from functional programming is actually easy to understand and grasp.

For example, immutability. This means that all your data can't change once it's created. Do you have a record with user information and the user changed their password? No, do not change the password field, create a new user record with the updated password and discard the old one. Sure, it does a lot of "create and destroy" sequences which makes absolutely no sense (why would you allocate memory for a new user, copy everything from the old one to the new one, update one field, and "deallocate" the memory from the old one? It makes no sense!) but, in the long run, it would prevent weird results, specially when you understand and start use threads.

(Basically, you're avoiding a shared state -- the memory -- between parts of your code.)

Another useful concept is pure functions. Pure functions are functions that, called with the same parameters, always return the same result, no matter how many times you call them. One example of a non pure function is random(): each time you call random(), you get a different number1. An example of a pure function would be something like this in Python:

def mult(x):
   return x * 4

No matter how many times you call mult(2), it will always return 8. Another example could be our immutable password change above: You could easily write a function that receives a user record and returns a new user record with the password changed. You could call with the same record over and over again and it will always return the same resulting record.

Pure functions are useful 'cause they are, first most, easy to test.

Second, they are easy to chain, specially in a data flow design: Because they don't have an internal state (which is the real reason they are called pure functions), you can easily call one after the other and no matter how many times you pass things around, they still produce the same result. And because each function, given the same input, produce the same result, chaining them all also produces the same result given the same inputs.

Just those two concepts can make code longer (again, you're creating a new user record instead of simply changing one field), but the final result is a more robust code.

1

Except in Haskell, but it does require sending the seed every time, so you end up with random values based on the seed, so even there it is a pure function.

Shortcuts Are Nice, But Only In The Short Run

A lot of languages/libraries/frameworks add a way to make things shorter, reducing the number of things you need to type.

But, later, that will bite you and you'll have to remove the shortcut and do the long things.

Frameworks and libraries -- and even some languages -- come with "helpers" for most boilerplate things. Instead of typing the same 5 lines of code over and over, you can use a simple function; instead of writing the function with 5 parameters, you can skip a bit and use another one with just one. Or you could just add a simple macro expansion on top of your struct/class and it would complete all the missing points.

Don't get me wrong, they are great.

But you must understand what the macro/function is hiding from you. 'Cause sooner or later, you'll find a case where it doesn't have a perfect fit and you need to change just a small detail. And then you'll start running in circles 'cause, well, how the hell the macro/function did that?

I've bitten before by Spring and Serde 'cause I started with the shortcuts without understanding what they were doing. And then I got a problem which the shortcut wouldn't solve, requiring me to go deep into the documentation. And because I skipped a few steps and jumped straight into the shortcut, it took me awhile to actually get what I needed to do different from the shortcut to solve my problem: I had no idea what the shortcut did and, thus, I had no idea what I needed differently from the shortcut to solve my problem.

Debuggers Are Overrated

I heard a lot of people complaining that code editors are bad 'cause it's hard to attach a debugger. I'd claim that this vision is wrong.

But let's take a thing out of the way beforehand: I'm not saying debuggers are bad you should never use them. Debuggers have their use, but every time I had to use one, it was because there was something missing.

Most recently, using a framework in Java, I had problems with my code. I'd expect it to crash 'cause I didn't handle things. What actually happened is that the framework silently hid the error and restarted the processing. To find out what was happening, I had to attach a debugger and see what was wrong with the data; otherwise, I'd have no idea about what's wrong.

Was a debugger necessary there? I don't think so. If the framework actually displayed the error (crashed, put a wall of text on the logs, whatever), I wouldn't need to use a debugger. But, because something was missing, I did, in fact, was forced to use a debugger.

Besides this, in the long run, you'd end up with problems in locations that you can't attach a debugger -- for example, your production environment. You could but you shouldn't do this. On the other hand, if you log events, then you can see what was going on, without a debugger.

Again, I'm not taking the merits of debuggers, but in the long run, they are mostly useless and actually point missing surrounding support to actually understand what's going on.

Think About The Users

Think how the data you're collecting from your users will be used -- this is more prevalent on these days, where "privacy" is a premium.

I once had a discussion with a CTO about collecting the user IMEI on our mobile app. Basically, there was no use case for capturing that information yet but, as he put at the time, "We may want to know if one user uses two phones, or if two users use the same phone". I raised the fact that we didn't need this information and, besides that, it felt like we were invading the users privacy. He still decided to go ahead. My answer: "I'll do it, but I want to point that I'm not happy with it."

In the end, the store blocked the app... because we were capturing the IMEI.

But there are cases and cases. If you really really need to capture user information, be sure to protect it against unauthorized use, be it by external forces (someone found a way to attack your data) or internal (some disgruntled colleague decided to take the data from your users with them).

And be sure, there will be a leak at some point, it's just a matter of time. If you can, the best way to protect your users data is to never capture it. When a flaw on your system is found or when some colleague leaves the company in bad terms, there will be no data to expose to the world, anyway. You can't be more secure than this.

Testing Software

To make sure your software works, you have to think -- and write -- tests. But are you actually testing something that is worth testing?

Unit Tests Are Good, Integration Tests Are Gooder

The view of the whole is greater than the sum of its parts. And that includes tests for the whole compared to tests of single things.

First, I just don't want to into a discussion about what's the "unit in a unit test"1, so let's take the point that a unit test is a test that tests a class/function, not the whole system from end to end, which would require data flowing through several classes/functions.

There are several libraries/frameworks that actually split this in a way that you can't test the whole. Spring+Mockito is one of those combinations -- and one that I worked with. Due the bean container of Java, the extensive use of Beans by Spring and the way Mockito interacts with the container, it's pretty easy to write tests that involve only one class: You can ask Mockito to mock every dependency injection (so it injects mocked beans instead of the real ones) in one class and mock every injected class, simply using annotations.

And this is cool and all and makes tests simple and fast. But the fact that we are making sure each class does what it should do, it doesn't give a proper view of the whole; you can't see if that collection of perfectly tested classes actually solve the problem the system is responsible for solving.

Once, in C++, I wrote an alarm system daemon for switches. There were three different levels of things the alarm system should do, depending on the incoming message from a service: It could only log the message of the incoming error, it could log the error and send a SNMP message, or it could log the error, send a SNMP message and turn a LED in the front panel on. Because each piece had a well defined functionality, we broke the system in three different parts: One for the log, one for the SNMP and one for the LED. All tested, all pretty. But I still had a nagging feeling that something was missing. That's when I wrote a test that would bring the daemon up, send some alarms and see the results.

And, although each module was well tested, we still got one things we were doing it wrong. If we never wrote an integration test, we would never catch those.

Not only that, but because we wrote a test that interacted with the daemon, we could get a better picture of its functionality and the test actually made sense -- as in, if you read the unit tests, they seemed disconnected from what the daemon was expected to do, but the integration tests actually read like "Here, let me show that we actually did what you asked". And yes, this was akin to Gherkin tests, although I didn't know Gherkin at the time -- and, better yet, we had tests that proved that we were following the spec.

Personally, I think over time integration tests become more important than unit tests. The reason is that I personally have the feeling2 that unit tests check if the classes/functions have adherence to the underlying design -- Does your view can actually work without the controller? Is the controller using something from the model or using things that should be in the view? -- but adherence to the design gets better over time -- developers start using the layout from previous examples, so they capture the design by osmosis, while the big picture starts to get more and more complex, with lots of moving parts.

1

There is no "unit" in "unit tests". "Unit test" means the test is a unit, indivisible and dependent only on itself.

2

Again, it's pure feeling from my experience. I have no data to back that affirmation up, so take it with a grain of salt.

Testing Every Function Creates Dead Code

If you write a test for every single function on your system, and your system keeps changing, how will you know when a function is not necessary anymore?

Writing a test for every single function on your system may come from the "100% Coverage Syndrome", which afflicts some managers, thinking that the only way to be completely sure your system is "bug free" is to write tests for every single line of code, till you reach the magical "100% coverage" in all the tests.

I do believe you can reach 100% coverage, as long as you're willing to delete your code.

(Cue the universal grasps here.)

But how do you know which pieces of code can be deleted?

When I mentioned integration tests, I mentioned how much more sense it made to me reading them instead of the "unit" tests, because they were describing exactly how the system would operate in normal (and some abnormal) conditions. If you write tests that go through the system, assuming it is a black box with an input point and an output, and you can get tests for all the normal cases -- and some "abnormal", like when things go wrong -- then you know that, if you run those tests and they mark some lines as "not tested", it's because you don't need them.

"But Julio, you're forgetting the error control!" I do agree, specially when you're talking with project owners or some other expert, that people will forget to tell you what to do in case of things going wrong -- say, the user typing their name in the age field -- but you can see those and you know that you need error control so you can add the error control and describe the situation where that error control would trigger.

If, on the other hand, you write a test for every function, when you do a short/simple check, you'll find that the function is still being used in the system by the tests, not actually, "value to the user" code. Sure, you can use your IDE to go back and forth between code and test and see if it points a use beyond the test, but it won't do it for yourself.

There is one other weird thing about using integration tests for error controls: Sometimes, you can't reach the control statement. It's true! I did wrote control checks for every function once but, when running in the integration tests, there was no way to produce an input at the input layer of the system that would reach the error control in that function 'cause the other functions, which would run before the one I was trying to test, would catch the error before it. If that's a design problem or not -- it probably was -- it's a different discussion, but the fact is that that function didn't need error control, something that I wouldn't see if I wrote test specifically for it, but it was clear in an integration test run.

Tests Make Better APIs

Testing things in isolation may give a better view of your APIs.

After reading the integration tests chapter, you may end up with the impression that I don't like unit tests1.

Actually, I think they provide some good intrinsic values.

For example, as mentioned before, they can provide a better look at the adherence to the design.

But, at the same time, they give a better view of your internal -- and even external -- APIs.

For example, you're writing the tests for the view layer -- 'cause, you know, we write everything in layers; layers on top of layers2 -- and you're noticing that you have to keep a lot of data (state) around to be able to make the calls to the controller. Or that you have similar calls, but the parameters are sometimes switched (say, one function gets a token and a user ID, and another function gets a user ID and a token -- why?) That's a sign that you may have to take a better look at the controller API.

Not only that, but take, for example, the fact that you're working on a library -- which will be called by someone else -- and you're writing tests for the most external layer, the layer that will be exposed by that library. And, again, you're noticing that you have to keep a lot of context around, lots of variables, variables coming from different places and similar calls using parameters in different ways. Your tests will look like a mess, don't they? That's because the API is a mess.

Unit testing your layers makes you the user of that layer API, and then you can see how much one would suffer -- or, hopefully, enjoy -- using that.

1

Again, let's ignore for a second that there are no "unit" in "unit tests"...

2

And layers all the way down, like turtles.

Make Tests That You Know How To Run On The Command Line

You know that "Play" with a little something on your IDE that runs only the tests? Do you know what it does?

A long time ago I read the story about a professor that taught his students to code. He preferred to teach using an IDE, 'cause then "students have to just press a button to run the tests".

I get the idea, but I hate the execution.

When we get into professional field, we start using things like continuous integration which, basically, is "run tests every time something changes" (it's a bit more than that, but that's the basic idea).

Now, let me ask you this: Do you think the students of the professor above would know how to add the command to run the tests in a continuous integration system?

I know I'm being too picky (one could even call me "pricky" about this) but the fact is that whatever we do today, at some point can be automated: our tests can be run in an automated form, our deployment can be run in an automated form, our validation can be run in an automated form and so on. If you have no idea how those things "happen", you'll need the help of someone else to actually build this kind of stuff, instead of having the knowledge (well, half knowledge, the other half is the CI tool) with you all the time.

Good Languages Come With Tests

You can be sure that if a language brings a testing framework -- even minimal -- in its standard library, the ecosystem around it will have better tests than a language that doesn't carry a testing framework, no matter how good the external testing frameworks for the language are.

The reason is kinda obvious on this one: When the language itself brings a testing framework, it reduces the friction for people to start writing tests, and that includes the authors of the language itself and the community.

Sure, better frameworks may come along, and languages that don't have a testing framework in their standard library may have options with better support and easier access but, again, when they are there from the start, the start is better and the final result is better.

Documenting your code

What does this piece of code do? Is "self-documenting code" actually real?

Documentation Is A Love Letter To Your Future Self

We all know writing the damn docs for functions and classes and modules is a pain in the backside. But realizing what you were thinking when you wrote the function will save your butt in the future.

When I say that it will save your butt, I don't mean the documentation will tell you something like "Here are the lotto numbers in 2027"1 or "If John complains about your future code review, here is some shit he did in the past".

I mean, it will explain how the flow of your code is expected to do. Imaging this: pick your code and replace every function call to its documentation. Can you understand what it is expected by reading that? If you can, congratulations, you won't have a problem in the future; if you can't... well, I have some bad news for you...

One point that may come here is "Code is its own documentation" or "self-documenting code". I do understand, and yes, simpler functions may make the documentation redundant (for example, if you notice that you need a function that multiplies two numbers -- and only do that -- giving it a description of "Multiples two numbers" may look redundant), but you have to ask yourself why you needed such simple function. Why it exists? Where it sits in the general data flow?

Another thing you can document: rarely used functions. One example is Java Collectors: In Java, you can create a stream of data, which you can apply transformations and such and, in the end, you may put the resulting collection of data into another structure -- a list, for example. The thing is, collecting to a list is pretty common, but collecting into a map, with a function as key and another value as value, splitting the result into two different data blocks, is not that common. Because it is uncommon to see such collector, it is a good idea to add tips on what each option is.

That's the things you need to document.

1

Please, don't make me revise this in 2027... :(

The Function Documentation Is Its Contract

When you start the code by writing the general flow as steps and making each step a function, you're actually making a contract (probably with your future self): I'm saying this function does this and this is what it does.

Remember that the documentation must be a clear explanation of what your code is doing and why it exists; remember that good messages will make reading the code only by the function documentation should be clear.

A function called mult, documented as "Get the value and multiply by 2" but, when you look at the code, it does multiply by 2, but also sends the result through the network or even just asks a remote service to multiply the incoming result by 2, is clearly breaking its contract. It's not just multiplying by 2, it's doing more than just that, or it's asking someone else to manipulate the value.

Now, what happens when this kind of thing happens?

The easy solution is to change the documentation. But do you know if people who called the function expecting it to be "multiply value by 2" will be happy for it to call an external service? There is a clear breach of "contract" -- whatever you initially said your function would do -- so the correct solution would be to add a new function with a proper contract -- and probably a better name.

If A Function Description Includes An "And", It's Wrong

Functions should do one thing and one thing only. I clear indication that you're breaking this principle is the need to add an "and" in its documentation.

This is kind like "sometimes rule", but most of the time, when you feel you need to add an "and" to the function documentation (its contract), then you're telling that that function is doing two (or more) things.

One of guiding principles of good code is the Single responsibility principle, in which each module/class/function should do one thing and one thing only. And, again, if you're saying that a function is doing "this" and "that", you can be sure it's not doing just one thing.

Ok, but what now? Well, you have two functions, with two distinct contracts. Ok, but you had those two being called, what happens now? Well, where you called one, you now will need to call two. If your preferred language have support for function composition, you can use that to group both functions again. This is the kind of stuff that you'll get when you learn to use functional programming.

Good Languages Come With Integrated Documentation

If you're worried about learning some new programming language, you can bet the one with a better documentation is the one that is born with a document processor.

Same goes for the frameworks/libraries of that language.

The answer for that is the same as languages that come with tests: because the programming language standard library comes with a documentation generator or even because documentation is bundled in the language itself, it reduces the friction needed to start writing the documentation.

Python is a curious case that it came with a simple documentation generator (PyDoc) and a bundled documentation format (DocStrings). Nowadays, almost nobody is using the default documentation generator anymore, but because the documentation format is still there and is still supported by the language (documentation appears as a property of every function, class and module), other tools took the post of default documentation generator, but the documentation format is still heavy used.

Also, the opposite seems almost always true: If the language doesn't come with integrated documentation, there is a very good chance that the documentation or the language or frameworks and libraries will be bad. Or, in the very least, every library will pick its own format, every framework will pick its own format and they will never match the language format, and you'll end up with a mess of a documentation to decipher.

Source Control

Programming is coding and coding needs to be stored somewhere.

Always Use A Version Control System

"This is my stupid application that I just want to learn something" is not even a good excuse to not use a version control system.

A very long time ago, using a source control system (or Version Control System) required installing a server, configuring it properly, installing the client and then you could keep track of the changes you were doing on your code.

Today there are lots of options that can work in a standalone fashion: Just install the client and you're done (well, mostly done, you still need to initialize the environment, but that is mostly straightforward these days).

And, again, there is no good reason to not start a project, as simple as it will be, without a version control.

The VCS will allow you to explore new changes without breaking the main code. It will allow you to save a half-way change to make a complete different change.

And, in the long, since you'll end up with working in team and will be required to use a VCS, you'll be used to using one.

One Commit Per Change

When working with source control tools, keep one change per commit. Avoid bundling more than one change in a single commit just to "save time".

I've seen my fair share of commits with messages like "Fix issues #1, #2 and #3". This is not something you should do. One commit for fixing issue #1, another for #2 and yet another for #3.

Just note that I said "one commit per change", not "one commit per file". Sometimes, to make a single change, you may need to change more than one file -- it may point that you have a coupling problem, but that's a different issue. You could, for example, make one commit which adds a new field in model without adding a change in the controller to load this field; after all, the controller won't (or, at least, shouldn't) break due the added field, and the model won't break (or, at least, shouldn't) break because the controller is not touching the field1.

When making a commit, think this: "In case something goes wrong, can I undo this commit without breaking other stuff?" Commit history is stacked, so obviously you'd have to undo the commits on top of that one. And that's alright.

BONUS TIP! If you're using git, you can use git add -p in case you "overchange". It will allow you to pick parts of a file, instead of adding all the changes in the file before committing.

1

Ok, it may have some issues if the field can't be null, but you get what I meant, right?

Gerrit Is A Mistake

I hate calling software "a mistake", but I can't find any other way to describe Gerrit. You may see people using Gerrit 'cause Google uses it. The thing is: Google misunderstood what Git actually is.

When Linus Torvalds came with Git, he was trying to mimic another system, BitKeeper. Along with some improvements over CVS and SubVersion, Git made really easy to create and merge branches, something that was either almost-not-supported or slow-as-heck, depending on which tool you look at.

You need to take this into consideration: Git made branches easy.

Then someone came with the idea of Gerrit: Instead of managing branches, it actually manages commits. Instead of having a branch for each feature, you should have one single commit as feature. You can have branches on your machine, but the server only deal with commits.

So Gerrit took Git, a tool that improved the way we deal with branches, and removed branches. This is akin to taking a text editor and taking away the ability to edit text. Does that sound right to you?

In my personal opinion, what they did was to take git apart and put an err in the middle: gERRit.

When I see someone using Gerrit, I know something is wrong there.

Git-Flow Is The Way To Go

If Gerrit is such a mistake, what can you use instead? Git Flow!

Git Flow is a plugin for Git for managing branches. It is based on the concept of "feature branches", in which each branch is a feature or bug you're working on. Once you finish it, it will just close the branch.

Although there is a lot to be said about Git and how you should use it, the fact is that Git Flow manages a lot of complexity of having a stable branch, an "unstable"/testing branch and all features around those.

Not only that, but with the current source control sites like Github and GitLab, the flow is quite similar -- although working with branches is changed with forks.

You can even install Git Flow and use it on your personal project -- which is something I do with this blog/book!

Project Organization

Everything is falling into place: You know how to code, you know what to document, you know how to test... but how do you put everything together?

Organize Your Code by Data/Type, Not Functionality

A lot of projects assume that you'll put things with the same functionality in the same place, no matter what data they deal with. This makes things harder to break apart later.

Most projects keep organized by the functionality each component do. For example, all the models are in the same place, all the functions that convert one model into an internal structure/DTO are kept together, and so on. Something like this:

.
+-- IncomingModels
|   +-- DataTypeInterface
|   +-- DataType1
|   +-- DataType2
|   +-- DataType3
+-- Filters
|   +-- FilterInterface
|   +-- FilterValidDataType2
+-- Processors
|   +-- ProcessorInterface
|   +-- ConvertDataType1ToDto1
|   +-- ConvertDataType2ToDto2
+-- OutgoingModels
    +-- DtoInterface
    +-- Dto1
	+-- Dto2

This is fine and works. But when you organize by data, it'll make a lot easier to split your project in smaller projects -- 'cause, at some point, you may want to do almost the same thing as you're doing right now, but with small differences.

.
+-- Base
|   +-- IncomingModels
|   |   +-- DataTypeInterface
|   +-- Filters
|   |   +-- FilterInterface
|   +-- Processors
|   |   +-- ProcessorInterface
|   +-- OutgoingModels
|       +-- DtoInterface
+-- Data1
|   +-- IncomingModels
|   |   +-- DataType1
|   +-- Processors
|   |   +-- ConvertDataType1ToDto1
|   +-- OutgoingModels
|       +-- Dto1
...

Now you can make a module that deals only with Data1, another that works only with Data2 and so on. And then you can break them into isolated modules.

And then when you have another project that also have Data1 but also deals with Data3, you can reuse most of the stuff in the Data1 module.

And I do understand that this creates an explosion of directories/packages, which may seem a bit unnecessary.

Believe me, I also thought the idea of keeping things by functionality made more sense. But in one project, I got a requirement to do almost the same thing as I was doing before, but with a small change, which would require one less step/transformation (in our example, you can think as the new requirement as doing exactly what the Data1, Data2 and Data3 did, with their transformations and such, but without the Data3 part). By breaking by their types, I managed to create small modules for each one and the new project would simply reference Data1 and Data2, but not Data3.

Create Libraries

One thing you must learn is how to break your project into smaller libraries, to avoid doing rounds to deal with "the same, but a bit different".

I've seen a lot of projects that use things like branches for different things. Say, you have an e-commerce page. But you also have different clients, and they all have different colours and logo. Some people would take this scenario and, using the VCS properties, use the main branch for the main code and a branch for each client, merge from main branch from time to time -- and, thus, the branches are never merged back.

This is suboptimal, 'cause that's not how VCS are supposed to be used.

But you can, for example, break the main code into a library/framework and have one project for each client, with their assets and you just reference the library/framework in each.

Simple and clean.

But stop there for a second. Although this makes the code cleaner, avoids duplication and uses a VCS in the way it was supposed to be used, you can't start this way.

Remember that future thinking is future trashing. What you can do is actually break your project by functionality, making modules related to their data and then, when you get a reasonable number of clients, you'll notice what can be reused in each, what modules make sense for one client and not for another. And then you'll have a good way to deal with those.

One project that may appear when creating libraries is "How do I create my own library repository?" 'Cause all modern languages today have support for importing external libraries and, even if your libraries will never be out of your control, they are external to the project. So you may need to learn how to deal with this before creating the libraries. And, unfortunately, each language and build tool has its own way to manage this.

Paper Notes Are Actually Helpful

I've tried to go paperless many, many times. But keeping a notepad and a bunch of post its in my desk has been one of the most helpful tools I ever got.

I've even managed to hide all my pens, move notepads to desks and use some note-taking application instead. In the end, none of those managed to come close to the utility of having something to scribble notes fast, or to draw a very high concept of whatever I'm trying to explain.

Also, a desk full of post its, or even a monitor with a bunch of things around gives the impression that you're really busy working -- just be careful to not have too many post its, or it will look like you can't complete anything. It even beats Trello!

Writing code

Let's put those things to work!

Be Ready To Throw Your Code Away

A lot of people, when they start with TDD, get annoyed when you say that you may have to rewrite a lot of stuff, including whatever your already wrote.

TDD was designed to throw code away: The more you learn about your problem, the more you understand that, whatever you wrote, won't solve the problem in the long run. Also, as you slowly solve new problems, you may notice some pattern in the code emerging (you're doing the same thing over and over, with only minor changes). That's a good time to go over and rewrite everything to take advantage of this pattern.

You shouldn't worry about this. Your code is not a wall (or any physical object): if you have to throw it away, you didn't wasted materials. Surely it means your time writing code was lost, but you got a better understanding about the problem now, or you may start to think in a more concise way to solve the problem.

Not only that, but as you progress through your project, solving problems and getting "acquainted" with the problem, you'll also notice that the spec will also change. This means that the problem your code solve wasn't exactly the problem you needed to solve; your code is trying to solve something that isn't exactly the problem.

Also, specs changing is really common. One thing that you can be sure is that it won't change everywhere. Some of the things you solved will stay the same, some others will be completely removed and some others added. And you will see that you'll refactor your code a lot, and throw a lot of code away. And not just code that solves the problem, but also the tests for that code.

... unless you focus mostly on integration tests.

Future Thinking Is Future Trashing

When developers try to solve a problem, they sometimes try to find a way that will solve all the problems, including the ones that may appear in the future.

Trying to solve the problems that will appear in the future comes with a hefty tax: future problems future will never come -- and, believe me, they will never come -- and you'll end up either having to maintain a huge behemoth of code that will never be fully used or you'll end up rewriting the whole thing 'cause there is a shitton of unused stuff.

Solve the problem you have right now. Then solve the next one. And the next one. At one point, you'll realize there is a pattern emerging from those solutions and then you'll find your "solve everything". This pattern is the abstraction you're looking for and then you'll be able to solve it in a simple way.

As Steve Jobs once said "You can't connect the dots looking forward, only backwards".

Don't Use Booleans As Parameters

When you're designing a function, you may be tempted to add a flag (a parameter in a function that it is a boolean). Don't do this.

Here, let me show you an example: Suppose you have a messaging system and you have a function that returns all the messages to an user, called getUserMessages. But there is a case where you need to return a summary of each message (say, the first paragraph) or the full message. So you add a flag/Boolean parameter called retrieveFullMessage.

Again, don't do that.

'Cause anyone reading your code will see getUserMessage(userId, true) and wonder what the heck that true means.

You can either rename the function to getUserMessageSummaries and have another getUserMessagesFull or something around those lines, but each function just call the original getUserMessage with true or false -- but the interface to the outside of your class/module will still be clear.

But don't add flags/Boolean parameters to your API.

Beware of Interface Changes

Interfaces and APIs is what you give away to others. If you keep changing them, you'll make everyone's life sad.

When talking about boolean parameters, I mentioned about renaming the function. If you control the whole source where the function is used, that's not issue, it's just a matter of search and replace.

But if that function was actually exposed in a library, you shouldn't change function names in a whim. That will break a lot of other applications beyond your control and make a lot of other people unhappy.

Remember, when you write tests for APIs, you can see these kind of changes happening and you can see the kind of changes you're doing on how they reflect externally.

You can create the new functions and mark the current one as deprecated, either by documentation or by some code feature. Then, after a few releases, you can finally kill the original function.

(A dickish move you can do is to create the new functions, mark the current function as deprecated and add a sleep at the start of the function, in a way that people using the old function are forced to update.)

It's Better To Let The Application Crash Than Do Nothing

Although that sounds weird, it's better to not add any error handling than silently capturing errors and doing nothing.

For example, a (sadly common) example of Java code:

try {
  something_that_can_raise_exception()
} catch (Exception ex) {
  System.out.println(ex);
}

This does nothing to deal with the exception -- besides printing it, that is.

The example may be a bit bad, 'cause Java forces capturing exceptions on functions that throw exceptions and it forces functions to mark themselves as throwing exceptions if there a throw in them.

But Python doesn't have this restriction and people still try to capture exceptions for doing absolutely nothing -- and, worse, just keep the execution going.

If the language allows it, you should let the application crash due the lack of error handling -- as long as you don't have any idea on how to handle it. Then, when they crash, you can think of a way to deal with it, instead of silently capturing it and doing nothing.

Also, keep in mind to not go forth and capture every exception/error in a single take -- like the example above, which will capture every exception, or like except Exception in Python. This last example actually happened to me when another developer added this "broad except"1 in a network code and, at some point, the code would get into the capture all the time. We checked every cable, every connection, every interface, till I noticed there was a syntax error in the code. In Python, syntax errors raise exceptions and, because we had a "capture all exceptions", we lost some pretty good time looking for the problem in the wrong place.

1

As called by Pylint.

If You Know How To Handle It, Handle It

If you know an error can occur, then you should handle it properly, instead of ignoring it.

This is the opposite point of let it crash: You're writing some code that you know it can crash in a certain way, what should you do? Well, the answer is simple: handle it, not ignore it.

If we go back to the fact that Java will describe every single exception that can be thrown by a function, you should handle each exception, no excuses.

If you're using Python, then you should capture the exceptions you know how to handle, no exceptions -- and tying with the previous point, if you don't know how to handle them, you should not capture them in the first place.

But, no matter what language you're using, if you know an error/exception can occur, deal with it. If you have to save the content of the user somewhere else, log it to be reprocessed later or even just show an error message, do it.

Although I seriously meant it, it doesn't mean you have to remember every single exception/error code and what it means when calling a function. You can write code that will actually go through the happy path and later fill the blanks. Or even, when you're working on another part of the code, if you remember another problem, just write on a post-it and add the handling later. The important bit is not to forget to handle it.

Types Say What Your Data Is

Memory is just a sequence of bytes; bytes are just numbers from 0 to 255; what those numbers mean is described on the language type system.

For example, in C, a char type of value 65 is most probably the letter "A", which an int of value is 65 is the number 65.

Remember this when dealing with your data.

And it doesn't matter of your language of choice uses dynamic typing or static typing. The same still applies.

One classic example of misusing types is adding booleans. Booleans are either true or false, but because most languages follow C, which doesn't have a boolean type and uses compiler pre-processors to define TRUE as an integer with the value 1 and FALSE with another integer with the value 0. Newer languages were build on top of what older developers knew, and so, a bunch of those languages also assumed using an integer under booleans was a good idea. And even today, with modern languages, people rely on those old methods.

Let me repeat that: You're adding booleans and expecting a number -- only because in the old times there wasn't boolean types.

No, you're counting the number of elements in the list 'cause that would see the whole list. You're not even filtering the false values over and counting the resulting list size. You're jumping the underlying type to get a bit of performance out.

Fortunately, some new languages are using booleans as a complete different type and wouldn't allow this kind of stuff.

If Your Data Has a Schema, Use a Structure

You may be tempted to use a list (or tuple, if your language allows) to keep your data if it has, say, only 2 fields. Don't.

Some languages allow unstructured data to be kept in the format of tuples: They act like lists, but you can use to store heterogeneous data (which is a cute way of "it stores fields of different types").

This languages also allow you to "destructurize" them, so you can extract elements from them without directly accessing them by index.

For example, in Python, you can have a tuple with:

a_tuple = ('A String', 1, 7.5)

And you can destructure it with

some_string, an_integer, a_float = a_tuple

See? It's simple! You don't need to create a whole structure if you're just passing a string, an integer and a float around.

Except, you do need a structure 'cause your data has a schema.

Tuples and destructuring should be used only when you need to pass data from one function to another -- and barely that. When you have this tuple being passed around, being destructured and created again -- say, you are adding one value of the tuple to another value and producing a new tuple in the same format -- then you have a structured -- and schemaed data.

And when you have a structured data, you must use a data class or a struct (or even NamedTuples, if you're using Python).

Although it may look way more simpler to keep destructuring and building the tuple over and over, in the long run you'll end up with a mess: a simple change -- like adding a new field -- will require checking every destructuring and every creation of the tuple to make sure if will stay in the same shape every time.

So: You data has a schema? Use a Data Class or Class or Struct. Only if it is schemaless, then you can use a tuple.

I've seen this used at least once. At the very start of the project, it may seem easier to just store the data as a tuple and destructure it and build it again when needed. There was even a whole module designed to receiving tuples, destructure them and rebuild new ones (for example, a function that would receive two tuples and compute the sum of the "value" field of each, building a new tuple as a result). But because of this design, to add just a new field, I had to change 14 files and do 168 changes around -- 'cause there was a function to add two tuples, but there were points where you need just one field, and there wasn't a function for it.

It would be easier to use if there were functions to extract each field, and add two tuples, and what else was needed for managing the tuples, but then you have to ask yourself: Why not use a class for that?

Don't Mess With Things Outside Your Project

Simple rule: Is the code yours or from your team? Good, you can make any changes you want. Does it come from outside? DON'T. TOUCH. IT.

Sometimes people are tempted to, instead of using the proper extension tools, change external libraries/frameworks -- for example, making changes directly into WordPress or Django. Believe me, I've seen my fair share of this kind of stuff going around.

This is an easy way to make the project -- the team project, that is -- a huge security problem. As soon as a new version is released, you'll -- or, better yet, someone who was not the person who decided to mess with outside code -- have to keep up your changes in sync with the main project and, pretty soon, you'll find that the changes don't apply anymore and you'll leave the external project in an old version, full of security bugs.

Not only you'd end up with something that may very soon put at risk your whole infrastructure, you won't take any benefits from things in the new versions, 'cause hey, you're stuck in the broken version!

Sometimes doing it so is faster and cheaper, and if you would do the same thing using extensions or actually coding around the problem, even duplicating the framework functions, would probably take longer and make you write more code, but in the long run, it's worth the time.

Sometimes the change you need is impossible 'cause the framework you're using doesn't have any support for extensions. This is the time you'll need to build a new layer on top of the framework. Again, this may seem painful and changing the framework directly is a lot easier, but you'll have to keep updating your patch for newer versions, which may not be that easy. Building on top of the framework will at least give you some assurance 'cause the exposed API must be way more stable than the internal code.

Resist The Temptation Of Easy

Sure that IDE will help you with a ton of autocomplete stuff and let you easily build your project, but do you understand what's going on?

I'm not denying the fact that IDEs make things easier. I'm trying to say that you should not rely heavily on their features.

I mentioned before that you should at least know how to run tests on the command line and the same applies to everything in IDEs: how to build, how to run, how to run tests and, let's be honest here, how to find proper names for your variables and functions. 'Cause it's nice that the IDE can complete all the names of the functions, but if the autocomplete feature was off, would you know which function you need? In other words, have you thought at least 10 seconds about a good name for your function so you won't need to use autocomplete to remember its name?

These days, IDEs can autocomplete almost everything, from function names to even how to name your variables. But using the autocomplete is not always a good solution. Finding better names is.

Start Stupid

One way to get away from the IDE is to "start stupid": Just get the compiler and get an editor (ANY editor) with code highlight and do your thing: Code, build it, run it.

Notice that say "stupid way", not "simple way".

Doing things in the stupid way is not the easiest way to start a project. How could one beat the easy of clicking a button and having the whole structure of a project done for you?

But starting it in the stupid way, in which you have to think your project layout, how to build stuff, how to run tests, how to do everything may give you some insights on how things work, how the pieces mesh together and how to cogs turn around. Even better: It make give you some insights on what doesn't work.

Honestly, you don't have to do this with all projects. You can still use your favourite IDE and do things in the easy way. But you can also have that side project on which you'll do everything in the stupid way, just to understand what your IDE is doing.

And when you grasp that, you'll be able to use any IDE.

Always Use Timezones With Your Dates

No matter if the date you're receiving is in your local timezone and you'll display it in your timezone, sooner or later, the fact that you ignored there was a timezone behind that date will hurt you.

(Note: Most of this post when I say "date" you can think of "date and time", although the date should also be "timezone aware".)

At some point of my professional life, ignoring timezones was easy: You just pick the date, throw in the database, then read it back and everybody was happy.

But things are not like this anymore. People will access your site from far away locations, the source of the date may not be in the same timezone of your system, your system may be running in a completely different timezone of your dev machine (it's pretty common to run things in our machines in the local timezone but the production system will run in UTC), the display may be a complete different timezone than your production and dev machine and so on.

So always carry the timezone with the data. Find modules/classes that support dates with timezones (a.k.a. make things timezone aware), capture the timezone as soon as possible and carry it around in all operations. Modules/classes that don't support timezones for dates/times should, as soon as possible, removed from the system.

Any developers a bit more seasoned -- and by "seasoned" I meant "Had to deal with times before" -- will probably claim "Hey, this is obvious!" And I'd have to agree. But it's annoying how many times I got bitten by some stupid bug 'cause we decided that "well, everything is in the same timezone, so it's all good".

Always Use UTF-8 For Your Strings

Long gone are the days where ASCII was enough for everyone. Long gone are the days where you can deal with strings with no "weird" or "funny" characters.

I became a developer in a time when the only encoding we had was ASCII. You could encode all strings in sequences of bytes, 'cause all characters you could use where encoded from 1 to 255 (well, from 32 [space] to 93 [close brackets] and you still have a few latin-accented characters in some higher positions, although not all accents where there).

Today, accepting characters beyond that is not the exception, but the norm. To cope with all that, we have things like Unicode and UTF-8 for encoding that in reasonable memory space (UTF-16 is also a good option here, but that would depend on your language).

So, as much as you to make your system simple, you will have to keep the internal representation of your strings in UTF-8/UTF-16. You may not receive the data as UTF-8/UTF-16, but you'll have to encode it and keep transmitting it around as UTF-8/UTF-16 till you have to display it, at which point you'll convert from UTF-8/UTF-16 to whatever your display supports (maybe it even supports displaying in UTF-8/UTF-16, so you're good already).

Today, I believe most languages do support UTF-8, which is great. You may still have problems with inputs coming from other systems that are not UTF-8 (old Windows versions, for example), but that's fairly easy to convert -- the hard part is figuring out the input encoding, though. Also, most developers tend to ignore this and assume the input is in ASCII, or ignore the input encoding and get a bunch of weird characters on their printing, 'cause they completely ignored the conversion on the output point. That's why I'm repeating the mantra of UTF-8: To remind you to always capture your input, encode it in UTF-8 and then convert in the output.

One thing to keep in mind is that UTF-8 is not a "cost free" encoding as ASCII: While in ASCII to move to the 10th character, you'd just jump 10 bytes from the start of the string, with UTF-8 you can't, due some characters being encoded as two or more bytes (you should read the Wikipedia page; the encoding is pretty simple and makes a lot of sense) and, due this, you can't simply jump 10 characters 'cause you may end up in second byte that represents a single character. Walking through the whole string would require traversing the string character by character, instead of simply jumping straight to the proper position. But that's a price worth paying, in the long run.

Optimization Is For Compilers

Let say you need more performance on your application. You may be tempted to look at your code and think "How can I keep this same logic and still remove a few cycles, so things seem to go faster?" Well, if you want performance, you need to change your logic.

But before jumping into the code, you may have to check your compiler options. Maybe you're not generating the optimized version. Maybe there is an option that you don't use that you can remove from the compilation.

'Cause "optimization" is what a compiler is for. They know where they can extract most of the underlying architecture, and people have been finding ways to make the compiled code more performance for decades. Heck, compilers can even delete parts of your code 'cause they can "see" that a piece of code will always produce the same result and, thus, isn't necessary and they will just put the same result where that piece of code was.

What you need to do is to think about a better design for your code, not how to improve the current code. And trying to trick the compiler by messing with the types, although may produce faster code, will really screw you in the future -- specially cause maintenance and code understanding will take long and figuring out what went wrong will always be harder.

Code is written for humans to read. ALWAYS. Optimization is what compilers do. So find a smarter way to explain what you're trying to do instead of using shorter words or messing with that your code is saying.

Units Makes Things Clear

You know what's one of the worst function names ever? sleep().

Sleep for how long? It is seconds or milliseconds?

Now let me ask you this: Would it clearer if the function was called sleepForMs()? Would you understand that the function would make the application sleep for a number of milliseconds?

What about sleepForSecs()? Do you understand that this will force your application to sleep for a number of seconds?

What if, instead of using the function name, you could use sleep("10s")? Does it make clear that you want it to sleep for 10 seconds?

That's why adding units to the function or parameters make sense. It removes the ambiguity of what it means and doesn't rely on some specialized IDE/Editor that display the parameter names.

If It Doesn't Run On Your Computer, You Have A Problem

I've seen a lot of systems that would never run on a isolated computer, like the developer tool, 'cause the system requires running on a specialized environment. Those things are wrong.

Requiring a specialized environment absolutely kills productivity.

If your system will run on a specialized environment -- and I'm including "the cloud" here -- look for something that can abstract whatever you're using. For example, if you're using AWS SQS, which is a queue, look for a library that can abstract the way a queue works so you can also run with RabbitMQ, which can be easily run on your own computer.

If you're using a very specialized thing, you may have to write the abstraction yourself, isolating it from the main system, so you can develop the main product in peace.

One of the most productivity killer environment I worked require running the project on a customized Apache installation, running the client specialized framework. The whole problem is that the client refused to allow us to not use it or install on our local machines (mostly 'cause the install of said framework was really complex). In other for us to work and see things working, we had to use a VPN to the client computers, develop things there and manually forcing things to reload. No only we had absolutely nothing to do when the VPN was down ('cause it require out company infrastructure working hand-in-hand with the client infrastructure and the internet provider infrastructure, which is absolutely impossible), the flow was really cumbersome.

If we had the chance to not use it and run all the development and tests on our own computers, I have the feeling we could deliver the product 2-3 months earlier.

Nothing More Permanent Than A Temporary Solution

Depending on where you look, "Nothing more permanent than a temporary solution" is either an old Russian proverb or a quote by Milton Friedman. Thing is, temporary solutions, unless you think about the future to fix them, will become permanent.

A temporary solution may appear either as a proof-of-concept or due some restrained deadline. You may create perfect system specs, you may have a perfect understanding of the whole in your Gherkin files but, at some point, you'll put some small script to fix a minor problem, or do a "not so good" solution to a point due to deadlines.

This happens and unless you take steps to get rid of those, you'll end up with a bunch of spaghetti code pretty fast. And that will snowball to a point that you won't be able to manage the project.

Once a scrum master suggested that we came with an idea to our product manager to do something akin to "Every three sprints, we'll focus on product value; the fourth one is ours to fix the things that are annoying us". I don't think we ever talking to the product manager about this, but we managed to open issues on our ticket system about the small warts we left behind, specially due deadlines. So there we had, a specially crafted bug type for "technical debt" which we never actually took the time to fix.

Unless you have a pretty good safety net to fix those, they will life forever. And it may be a better option to tell "we can't deliver in time" than adding (yet another) temporary solution, as hard as it is to convince the higher ups that you can't deliver the product with a temporary solution.

Making Things Go

How to make things easier for you when you already have the application in order?

The Config File Is Friend

Do not ignore the power of configuration files.

Imagine you wrote a function that you have to pass a value for it to start processing (say, a twitter user account id). But then you have to do that with two values and you just call the function again with the other value.

It makes more sense to use a config file and just run the application twice with two different config files 'cause, this way, you have a single, small, testable application instead of two, or a very complex application that does a lot of stuff.

We can even jump into the idea of creating libraries and say that, instead of splitting your e-commerce application into smaller parts and making a big one by grouping these smaller parts, you could simply have one e-commerce application and, for each of your clients, you would have a different configuration file, pointing to different assets. This way, even the assets may reside in the same repository in the same branch, 'cause all that identifies which assets should be used are defined in the configuration file.

"But which one should I use?" you may ask. Well, "it depends". It may make sense to have one single application with different configuration files if most of its can be used all the time. If the intersection of used things is very small, it may make more sense to split into different libraries and just "pick and chose" what to use.

But besides the replacement of libraries, you can also think things like: "Ok, I have to remove elements after a while1; but which would be a good time that they can exist before I can remove them?" Well, if you're not quite sure (and, sometimes, even when you're sure), you can use a configuration file to define how long those elements will stay in the system before being expunged. Maybe you're not even thinking about how long each element will stay in the system, but how many of those elements you'll keep in the system before removing the old ones -- which is, again, a good candidate to be moved to a configuration file.

Configuration files allow you to change properties of the system without recompiling everything. And, if in the future you decide to follow the 12 Factor app, you'll find that you're half-way through it.

1

In other words, they have a time to live.

Command Line Options Are Weird, But Helpful

In this day and age, when everything has a graphical interface, does it still makes sense to add command line options to your application? In fact, it does.

When I mentioned the configuration file, you may have thought about using adding a default path for it and using the same file over and over.

Well, that's not wrong, but what if you want to use a different configuration? Would you keep moving the original configuration file to another place, moving your configuration back and keep this back and forth? Keep both versions and just use a symbolic link with the configuration filename pointing to the one you want?

Why not add a command line option in which the user can select which configuration file should be loaded?

This would make their life and yours easy.

Also, be aware that, today, there may be libraries to handle command line in every language, which will help you build a good command line interface, along with standardizing it to have the same interface as other applications.

Not Just Function Composition, But Application Composition

When we were discussing the magical number seven, I mentioned that it made more sense to actually call the functions in sequence instead of each calling the next. That's basically a "function composition", one thing you can also do with your applications.

Unix came with the idea of "applications that do one thing and do it well". And then you could just pick the output of one application and plug it as input of another (and then plug the output of the second into a third, and so on).

Also, I mentioned that you could use configuration files to do the same processing over different source elements (based on a configuration, that is) instead of writing an application that would process both in a single shot.

One problem with that approach is that you may need both results to actually produce a usable result (for example, how would you build a list of common followings of two Twitter users if you don't have both lists?).

That problem can easily be solved if you write a different application that just receives both lists and compare them. That would greatly simplify your general codebase 'cause instead of one massive codebase with lots of moving pieces, you'd have two small codebases, with less moving pieces. One could still break the other -- say, if you or someone else changes the result of the first function -- but you will still get the results of the first without missing the whole 'cause the second is breaking.

PS: I reckon it's really hard to create application composition with graphical applications (why would you ask your user to have two applications open at the same time to make something work?) but you can extrapolate this for almost everything else.

Even for Application Composition, Start Stupid

Application composition may lead to microservices -- which is good -- but microservices require some ideas about how applications "talk" between them over the wire (protocols and such) which you don't need to start with.

Again, because you just want to simplify your work, you can make the applications use files directly: Have your first application generate two files and the second application receive the file names from the command line. There, simple and stupid, and works.

You can even make the first application, instead of generating a file, just send its result on the standard output, and have the second application receive the data from the standard input -- both of which are managed as files, anyway. Then, with a bit of magic, you can put everything together without wasting space.

Worry about talking over the wire later, when you understand how networks work.

Logs Are For Events, Not User Interface

Two things in one: First of all, when using logging, use it to log events, not for user interfaces; second, log events in a machine readable way, not necessarily an human readable format.

For a long time, I used logs to show to the user what was happening. To me, it was logical to use something where I could mark errors as errors, general information as information and, if the user requested more information, print more information on what was going on. So I just added logging, defined normal messages as info, errors as errors, information that may help me find errors as debug and use only the logging system for all output of the application.

But that's not what logs are targeted for -- and now I'm having to rethink most of the stuff I already wrote.

Use the standard output to inform the user what's going on, in a human readable format; use the standard error output to inform the user when things go wrong; but use the logs to capture something that you'll have to process later, so you should probably use a format that it is easier to parse, even if it is not so friendly.

As an example, let's say you're connecting to a server. You could use the standard output to say "Connecting to server", to give the user a feedback about what's going on; at the same time, you could log "CONNECTION [SERVER]", with the IP/Name of the server you're connecting. Surely, the "CONNECTION" word is not that friendly to the user, but if you had to parse the line, it would be really easy, wouldn't it?

Another example: If your application is adding a record to the database, there is nothing wrong logging "ADDING_RECORD: field=value; field=value; field=value" 'cause, in case something goes wrong while saving the record, you could have the values to try to figure out why it failed -- surely, logging why it failed also helps, but you know what I mean. This is an example of something that makes complete sense in logs, but not in user interfaces.

Be Transparent With The User

Since we are talking about logging, another thing you must do is to be transparent with the user in your user interface.

And by "be transparent", I meant that your website/mobile app needs to point out to the user that the webserver is down instead of saying to the user to check their internet connection; your application is getting something from the webserver, so you can say "Oops, something wrong on our side".

Another example: If you need to check a bunch of data before saying "It's done", add a counter to show the user that the application is doing something. Joplin, when syncing data with a webdav server, needs to check a bunch of files; one version would simply sit still with a spinner on "Syncing" and nothing more; when they added a counter, I could easily see that there was something going on.

Those small details, for as bad as they may make you look, will win points with the user in the long run.

One Version To Add, One Version To Remove

A lot of things change during development. One day you need a field, another day that field may be completely different. For those cases, use one version to add the new field and another to remove.

You have a database with a lot of customers and their ID is numerical. But for some reason, they now need to be strings. Instead of changing the field type and doing a whole migration, make a deploy with a new field, in which you'll keep the old and the new format going on and, in the next release, remove the old field. No downtime. You can even run the migration while the system is up, since the new field won't be used.

(I'm simplifying the problem a lot here, 'cause the customer would have references all around your system, but you get the point, right?)

I had a problem in which we store the link for an object directly in the backend (we shouldn't, that's a frontend problem, but that's a discussion for another time); our interface is changing and so should the link. If we did a change in the link directly, that would mean the backend would have to be deployed at the same time as the new interface; by adding the new link format in another field, we can deploy the backend easily without breaking the current system.

Learn To Monitor

On a previous life, to understand how a system behaved, I added a ton of metrics: how fast things were going in, how fast things were going out, how many things were in the middle, how many the job processed... Not doing it so makes me feel... naked.

Monitoring your project performance give you a really good view of how a system is behaving. Is the speed going down? Is the system taking longer to process an input? Are no inputs being processed?

If you have this kind of information, you can check what is going on in the system and understand why. Is it normal? Did a change around the system (the other system that produces the input, the system that consumes in the output) affected the results?

If you're not measuring, you'll have no idea.

Also, "If you can not measure it, you can not improve it", as Lord Kevin said.

Community/Teams

Programming is barely a solo endeavour. You'll have to deal with more people when working on your projects.

A Language Is Much More Than A Language

Picking a programming language is much more than just picking the words that will generate a code. They come with a community, a leadership, an ecosystem and a thread the binds them all together.

Programming languages, in essence, are simply a bunch of keywords that make things "go". But besides those keywords, they also bring their community, the way the leaders deal with the community, the tools created by the leaders or community to deal with the minutiae of creating a system, the way those tools interact with each other, and a lot more.

While a language may have a simple syntax, it may be that the ones controlling the language actually don't give two shits -- if you pardon my French -- to the community. They focus on solving their problems, not the community problems1.

Or maybe the community has duplicate tools -- which is not a problem -- but that developers of each tool don't talk to each other. Or worse: They simply refuse to look what other tools are doing, which could be used to improve their own2.

And maybe that third language is not as simple as others, but the leadership is always discussing things with the community, being transparent on their decision, allowing the community to discuss the future of the language and even different groups building tools decided to merge efforts to give the community better tools.

That's why you can't "pick" a language by its syntax alone. That's only the surface of what the whole of a language encapsulates and if you ignore the other elements in it, you may find yourself with a cute language in a community that is always fighting and never going forward.

And picking a language for something above the syntax is even worse.

1

Yes, this is common, even in larger communities. And yes, I've seen the leadership ignoring requests from the community and, sometimes, just ignoring all the hard work the community did to supply the missing bits because they didn't like it.

2

Again, I've seen this before: There was a language that didn't come with a build tool bundled. The community created a tool, which was widely adopted. Later, a new build tool appeared and, in one of the notes, the author of the new tool mentioned a feature. The community came and asked "The previous build tool did something like that, what's the difference between that and your tool?" And the answer was "I never used the first tool." So, basically, the community ignored whatever the community was using.

Understand And Stay Away From Cargo Cult

"Cargo cult" is a type of cult which appeared in the Melanesia, in which the natives would build their copy of an airplane (no motor, 'cause they didn't have the knowledge to build one -- or even knew what went inside the airplane) in the hopes they would get the same results as a real airplane.

In IT, a "cargo cult" is the expectation that if you use the same tools as some big players, you'd end up getting the same results.

One example: Netflix runs a large fleet of microservices daily; they use Spring Cloud; if we use Spring Cloud, we can also run a large fleet of microservices.

Although it may sound correct in a first glance, things are not like that. There is much more to the Netflix fleet than just Spring Cloud.

Sometimes, cargo cult can appear in a form of "fanaticism" about celebrities: Fowler said such and such pattern works this way and that's exactly what we should do. Just because Fowler is well know software engineer and architect and do have some very clever ideas, picking them and running exactly the way he described may do more harm than good -- basically, 'cause you'd end up applying a design pattern without worrying about solving your problem in the first place.

Another example: "ProductX is sponsored by BigCompany, so it's good". It may be, but the fact that BigCompany is being ProductX doesn't immediately makes ProductX good, or even if it fits your solution. And there is much more behind a product than just its development.

"Right Tool For The Job" Is Just To Push An Agenda

A lot of times I heard "We should use the right tool for the job!" Most of those times it was just a way to push an agenda.

When someone claims we should use the "right tool", the sentence mean there is a right tool and a wrong tool to do something -- e.g., using a certain language/framework instead of the current language/framework.

But sadly, none of those times it was really the "right tool". Most of the time, the person saying we should use the "right tool" was trying to push their own favourite language/framework, either because they disliked the current language/framework or because they don't want to push the "hero project".

The Right Tool Is More Obvious Than You Think

Maybe you're in a project that needs to process some text. Maybe you're tempted to say "Let's use Perl" 'cause you know that Perl is very strong in processing text.

But that may still be not the right tool.

Although Perl is an amazing tool to process files, providing every single switch and option you'll ever need, you're missing something: You're working on a C shop. Everybody knows C, not Perl.

Sure, if it is a small, "on the corner" kind of project, it's fine to be in Perl; if it is important for the company, it's better that if it is a C project.

One of the reason your hero project may fail is because of this: You may even prove that what you thought it was a better solution is actually a better solution, but it can't be applied 'cause nobody else can maintain it.

Code Reviews Are Not For Style

When doing code reviews, do not focus on style; focus on design things that look a bit weird.

Code reviews are designed to spread knowledge around the team, with focus on construction and problem description. Does the sequence of code gives you an understanding on what it is trying to do? Does it contains something you know it will make things harder to read in the future? Does it miss the point?

That's the things you should focus.

If the author of the code missed a space here, left a blank line there... that's of no consequence in a code review. This is not the thing to discuss in the code review.

And people hate people who go through code reviews just to point missing/extra spaces and lines.

On the other hand, if you find something weird in the code which is something you want the author to recheck, then you're free to comment that "it would be good" if they fix the style. But that's it.

Code Formatting Tools Are Ok, But No Silver Bullet

One thing a team may decide to fix the continuous flux of code style comments in a code review is to use a code formatting tool to auto-format the code. That's ok, but they should never rely on it.

Now yeah, that kinda solves the problem, but there is one small problem: we, humans, are not as flexible to read code as computers are; what is readable by a computer may not be readable by a human. Surely they try to create some heuristics on what is good for human reading, but that doesn't mean it gets right.

Also, unless you start from scratch to use the auto-formatting tool or do a change in all files in one single go, you should never assume it will do a good job.

I've seen tools like this implemented in a commit hook, in a way that the tool would reformat the code just before adding it to the repository. The biggest problem is that, in that team, we didn't run the auto-formatting tool in the whole project before hand, and we also added a coverage tool (that checked the coverage on the changed parts of the file) without every running the coverage tool on everything. The result is that, suddenly, a lot of commits got refused because the auto-formatting tool was changing lines that the developer didn't change (it changed old code) and suddenly the coverage tool noted the missed tests and decided it was no good.

So good, punctual changes were suddenly being blocked 'cause instead of doing the whole thing in a single shot, people decided it was a good idea to let the code evolve till everything fixed itself.

On top of that, some people who were neither in the mood to actually add the tests or worried about style found a way to do the commits without running the hook, so they basically skipped the whole "let's improve our code" and let fuck all.

So, it's ok if you run the auto-formatting tool for yourself, but you need to have the maturity and responsibility to watch yourself and be willing to fix and take responsibility for other people's code when the formatter changes their code.

Code Style: Follow It

If your project have a defined code style, you must follow it. Sometimes it may not be clear ("this struct/class should be singular or plural"?), but do your best to follow it.

If your project doesn't have a style, maybe it's time to pick one. There are well established styles for almost every language today, so you can start with that. You can even make your changes, but you need to realize that since it's been established for a while, a lot of other people are using that style and, thus, if you keep as is, your code will mesh better with the rest of the ecosystem.

And remember that even your stupid code is part of the ecosystem of the language and the better you interact with the ecosystem, the better citizen in the ecosystem you are.

TIP: If you don't have a code style yet, and you're using a language that's derived from C or C++, use K&R Style; if you're working with Python, there is only one style: PEP8.

... Unless That Code Style Is The Google Code Style

Every freaking time Google comes with their own coding style, it's a garbage fire. The community came with a better style way before and Google seem to come with a style with high contrasting parts just to call it theirs.

The only reason to use Google Code Style is in case someone less smart than you decided it would be a good idea to use it. Then, I feel sorry for you, but you'll have to follow Google Code Style.

Hero Projects: You'll Have To Do It Yourself

An "hero project" is a project/spec change that you personally think will solve a group of problems in your project. It could be a different architecture, a new framework or even a new language.

Hero projects happen mostly when a single developer wants to prove something without the support of the company or even the time they are in.

On those projects, developers will spend their free time to write a proof-of-concept, just to prove a point.

And, sometimes, it just proves that they are were wrong.

(Although that last point sounds a bit sad, if you have to do an hero project, you'll still learn something new and, maybe, even add a new bullet point to your CV.)

Just to be clear: Sometimes an hero project will fail because the answer is obvious. Don't let that make you feel down.

Global Changes Must Be Discussed With The Whole Team First

So you got tired of bad tests and decided it is a good idea to add some fuzz testing tool. Before you do add it in the main branch, you have to discuss it with your team.

It's mind-bogging that some people think something it's so good that they don't need to discuss with the whole team about it; they simply do. They don't seem to care that people have their workflows and changing something would break them. But hey, I've seen it so many times it is not even fun.

And let me clear here: You need to discuss it with the whole team, not just some of it (excluding people on vacations, 'cause you don't want to call them just to tell them something will change). Worse: Don't discuss only with those that will agree with you; you may not have seen all the problems those changes will inflict on the other devs workflows but, by bringing that with those that may not agree with you, you may gain some more insights on what could go wrong.

Also, focus on what would be the gains and the loses. "We'll get better tests, but you'll have to take a bit more care on the way you write tests" is a good explanation, specially if you show the changes people will have to do in future tests. Also also, notice that I said future tests: if you want to implement something new, you must be sure it won't require everyone getting out of their way to make your idea work -- don't make people rewrite tests 'cause they will break; don't make the other devs reformat their code 'cause you decided, alone, to add a linter to your CI with your own rules; don't make people worry about unwritten tests 'cause you decided it would be a good idea to add a code formatting tool and that would make your coverage tool think they are changing some unrelated piece of code that wasn't untested before.

Don't be a jerk thinking you know more than your whole team.

Personal

Companies Look For Specialists But Keep Generalists Longer

If you know a lot about one single language, it may make it easier to get a job, but in the long run, language usage dies or loses its charms and you'll need to find something else. Knowing a bit about a lot of other languages helps in the long run, not to mention that may help you think of better solutions.

Even if you're in a shop that is mainly in one single language, that's no excuse to not check other languages. But, then again, learning languages that are just small changes on the current language would not help you either.

Alan Perlis, author of the ALGOL language, has one excellent phrase: "A language that doesn't affect the way you think about programming, is not worth knowing."

I still maintain one single rule for programming languages: The language I use at work must not be the same language I use outside it1. That simple rule made sure I was always learning something new.

Learning a new language can also help you understand things in some language you used before: Rust help me understand how generics works in Java; seeing how to do dependency injection in C++ help me understand how Spring does it in Java.

On top of that, because I was always learning something new, moving between projects was something that happened a lot. At one point, I was hired to work with Python, but the contract was taking too long to be signed, and my manager asked if I could help some other team with their iOS application. Because I did learn a bit about Objective-C, surely I could help. Later, another project in C show up and guess who also knew C?

1

... which led me into some sad times when I was working with Python.

Keep A List of Stupid Bugs That Took More Than 1 Hour To Solve

If it took you more than one hour for you to figure out what went wrong, it is a good idea to put it on list, 'cause these things have the tendency to appear again.

I must admit that this is one thing that I should be doing, but I keep forgetting. The worst part: It usually takes me about an hour to figure out what went wrong, only to realize by the solution that I had the same problem (with the same solution) before and it took me about one hour to figure out what went wrong.

If I just had a list of stupid bugs that took me about 1 hour or more to solve, I wouldn't be stuck for another hour figuring out what went wrong.

When It's Time to Stop, It's Time To Stop

Learn when you can't code anymore.

Learn when you can't process things anymore.

Don't push beyond that, it will just make things worse in the future.

I tried to keep coding once when I had a migraine (not strong, but not mild). Next day, when I was better, I had to rewrite most of the stuff I did, 'cause it was all shit.

Also, when you're not feeling fine, you won't be able to concentrate and your productivity will plunge. It's a lot better to be a burden to society at your own place than a burden to society at work.

Code of Conduct Protect YOU, Not THEM

When you're beginning with any language/library/framework, check their CoC; they will protect you from being harassed for not immediately getting what is going on instead of blocking you from telling them what you think.

I'm mentioning this 'cause a lot of people complain about CoC, but they forget that they allow them to join in any project without being called "freaking noob" or "just go read the docs before annoying us".

And don't be afraid to ask moderators if someone that seems be in the community for longer than you isn't break the CoC. Just because you just got into the community, it doesn't mean you can't be part of it or that someone can abuse their position of "veteran" to not respect you.

Also, remember that most people that are against CoCs are the ones that want to be able to call names on everyone.

Learn To Say No

Sometimes, you'll have to say no: No, I can't do it; no, it can't be made in this time; no, I don't feel capable of doing this; no, I don't feel comfortable writing this.

Saying no doesn't mean you won't do it. Once I had to say to our CTO: "Ok, I'll do it, but I want to note that I don't agree with what we are doing." In the end, the app was barred exactly because the thing we were doing.

Being explicit about what you don't feel is a good point may not be what some higher ups are expecting. The fact that you don't approve but will do it anyway may be something that can either show that your not simply a drone or, unfortunately, label you as a "troublemaker". Honestly, if you feel it threw you in the former, you should start looking for a new place to work. If you said you won't be comfortable and still did the work, and they had to label you something, then this place doesn't respect you as a person.

Take Responsibility For The Use Of Your Code

This is hard. Very very hard. It's the difference between "freedom" and "responsibility".

There is nothing wrong in writing, for example, a software to capture people's faces and detect their ethnicity, but you have to think about what that will be used on.

Even on an open source project, you can take responsibility without blocking people. You can make your project harder for people trying to abuse to use it, to the point they will have to take control of their own fork.

One example is a small application called Tusky, which is "An Android client for the microblogging server Mastodon", completely open source. Mastodon is a network of microblogging servers with connect to each other, kinda like Twitter, but you can pick a different server that is not twitter.com and still get updates from that server. One of the servers that appeared in the server list is an alt-right server which, as most alt-right forums, promote a lot of hate. What Tusky did? When you try to add an account on that server, instead of adding the account, they play a video of Never Gonna Give You Up, basically rickrolling anyone who, well, is an alt-righter.

Tusky broke the open source license? No, the code is still available. Anyone wanting to use the server can pick the code, fork it, remove the rickroll and release their own version of the application. But Tusky developers took an instead of saying "We'll not take part in promoting hate speech" and one can't deny that they did.

It is a bit hard to do so on the company code -- you would get some reprimands if you try to shame or block one of the company clients from using the company application -- but you can say no and, depending on how offensive you think the use the code is, you can even start looking for a new place to work. People on larger and "cooler" companies, like Google, left their jobs because they didn't agree with what the company was doing, and so can you.

Don't Tell It's Done When It's Not

You are tired of running the same thing over and over again. You kinda remember that something weird may happen, but because you're tired, you tell everyone that "It's finished". Don't.

I must admit that I've done this. Yes, there is that corner case that I didn't test. Yes, there is that piece of code that, although it works, it's weird. Yes, I left a technical debt behind. But we get tired of looking at the same thing over and over. And we get pushed by higher ups to deliver stuff. And that's when we say "It's done", when it's not.

Although we just dodged one, we end up leaving our colleagues -- people that really depend on our work -- down. They expect to connect to whatever we are doing to keep going. The expect to see the results of their work crystallized by ours. And they can't. 'Cause we said "It's done" when it wasn't.

And you can be sure, as soon as that happens, they will be the first to point, in minutes, that it is not done.

On the other hand, if you say "Almost, but there is this thing that bothers me and I think it may give us a headache in the future", they will understand. So be clear and transparent about your work.

People Get Upset About Code And Architecture Quality 'Cause They Care

At some point, you'll describe some solution/decision about some piece of code or some architectural design and people will seem annoyed/pissed about it. When people care about a product/code, they do that.

Or maybe you will get annoyed/pissed.

I think one of the nicest compliments I ever got was "You're annoying 'cause you care" when we left a meeting in which we decided to cut corners and do things halfway to beat some deadline -- or just 'cause people were not in the mood to do things in a more complete way.

You'll Learn About Yourself The Hard Way

We get frustrated with code that doesn't compile. We get angry with customers asking things back and forth. We get upset when upper management can't make up its mind. And we lash out on others when that happens.

Unfortunately, that's the way you'll learn about yourself. You'll learn to figure out when you get frustrated. You'll learn how you can keep getting attacked by others -- even micro-aggressions -- but, without realizing, at some point you'll suddenly burst into an counter-attack that may seem out of proportion.

The worst part of it all? Very few people will tell you what you're doing before it's too late -- you'll get in trouble way before you could actually understand what you were doing.

But, again, you'll get in trouble.

And you must learn about these events. Take the feedback as true, even if you don't agree with it. I had my falls, and I always took it as something I need to improve, even if later I said to my psychologist that I thought it was unfair and biased -- mostly 'cause I'm the quiet guy that always took the blows and never complained about, and people taking the flak from me were more vocal and friendlier to the higher-ups. But, again, I took it as true, and did my best to work on those problems.

Pay Attention On How People React To You

One way you can learn about yourself is to pay attention on how people react to your actions.

I have a "angry man resting face", which means that, even when I'm in a null mood, it looks like I'm angry.

I already had one meeting and which I started to ask something and noticed that the other person move a bit back. That's when I realized that didn't sound exactly how I meant. I had to add "I'm not saying what you're proposing is wrong, I'm just confused."

Also, I got a manager once come up with "I thought you were the serious person... till you suddenly started singing in the middle of a meeting"1.

You need to keep an eye on this. How is people reacting to your reactions? Are they opening themselves to you or are they closing?

1

I have this "serious" problem that, depending on the word someone says, I recall some lyrics and suddenly start singing it.

Don't Confuse Hero Project With Hero Syndrome

Someone that suffers from Hero Syndrome will claim that things won't work unless they are carefully watching over everything.

I've seen this at least two times in my professional life. Usually, those people are actually doing so much micromanaging that they are not other realize when things are in trouble.

I've even seen someone doing a poor job on their job, so things would break and then start calling people out that he had to fix it 'cause nobody would.

Don't do that.

I know you can get frustrated when you're the only one realizing things are breaking apart, but you can add some monitoring to your project, asking your manager to show your solution -- you can even mention it on your group/daily stand up meeting -- and pointing out to people how to realize when your project broke. Suddenly, people will realize how to monitor theirs too.

Beware of Toxic People

You'll find people that, even if they don't small talk you, they will bad mouth everything else -- even some other people -- openly.

Toxic people love drama. They love to put people down. They love to point mistakes made by others -- but never by themselves. Some of them actually do that to make themselves look better in the eyes of the upper management.

Not totally toxic, but I did work with people who would never answer an email unless the manager was in the discussion. Another person would always claims his team did everything they could, even putting himself at the disposal of the manager to solve any issues, and that the problem was not related to their work -- which we proved three times it was.

You need to stay away from those people. They will harm in ways you can figure out immediately. Their attitude towards other (and maybe even yourself) will drive you so down you'll waste more time wondering what you did wrong than doing your job.

One thing to take a lot of care: Even if it is not your intention, you may not realize that you may be seen as toxic 'cause you don't understand yourself yet and the way people react to you.

Beware of Microaggressions

Microaggressions are defined as "brief, everyday exchanges that send denigrating messages to certain individuals because of their group membership". The hardest part is that they don't sound aggressive.

Although I'm not part of an oppressed group, I've been microattacked more than once, by the same person.

At some point, he mentioned that one couldn't talk bad about someone "around some people". That "someone" was a politician that got arrested for, basically, stealing from the country to promote a certain other company. The way that person said it, thought, made it seem I felt it was wrong to arrest the politician.

Another time, I was casually saying that I shouldn't have come to work on my motorbike, although three forecast apps said it wouldn't rain. His comment: "I just looked outside".

It may seem innocuous reading those, but if you look closely, all he was trying to do was do let me down. Oh no, I'm part of a group that thinks politicians shouldn't be arrested! Oh no, I'm not smart enough to look outside, while he is1.

And those are really hard to fight, 'cause we aren't prepared to "get" those as real attacks.

On top of that, HR people are not really prepared to understand those (it will always fall into the "it was just a joke"2 excuse and you'll be the troublemaker3).

The worst part? While you don't fully get it as an attack, it will slowly pile up. At some point, you may even burst into attacking the person back, with all the concentrated attacks in a single moment. And them you will be seen as aggressive, not them.

Better just stay away and avoid contact if possible.

1

In the end, when I had to leave work to go back home, it wasn't raining.

2

... which is the pure definition of "Schoddinger's Asshole": Someone that make a comment and, by the other people reaction, call it a joke as a "get out of jail" card.

3

And I do wish you have a HR department that understand microaggressions.

Toxic/Aggressive People Are Not Fixable -- Unless It's You

You may think "But I could go to those people and say 'Why are you being toxic?' or 'Why are you attacking me?' or even just tell them it's not nice to say such things. It would help."

I don't believe that's the case.

In the case of toxic people, they just want to see the place burn so they can be the hero that saves the day. Microaggressors just want to make your feel down 'cause so they could feel superior to you.

And I don't think they can be easily fixable. That's their modus operandi and they will keep doing it so 'cause that's the only way they can see to "improve" themselves -- even if there is no real improvement, they are just letting everything down so they seem better than the others.

On the other hand, if you keep paying attention to the way people react to you, you may notice that you may be doing this to others. And now the ball is in your park: Do you want to be a better person or not?

Realize When It's Time To Quit

Instead of taking the blows and keep moving, maybe it would be better to your own health to simply quit.

Unexpected circumstances caused a delay on your task and your boss lashed at you.

You need to keep avoiding a guy that keeps bad mouthing some minority, something that you don't agree.

Another guy keeps an aggressive posture around women, and you know that's not something nice to do.

Yet a third one keeps complaining that, when he's not around, things don't work.

I've to say it: You're in a toxic environment. Even if the pay is nice and the project is interesting, it's not worth your health. You'd end up being a constantly pissed off, annoyed person on your forties (cough).

I.T. World Is Really Small

We have two expressions here: "The world turns around"; it means whatever you do, sometime in the future, you'll face the consequences of it. Another expression is "The world of something is an egg"; because the world turns around, if the world is an egg, you'll face the consequences sooner than you think.

What do I meant with those two expressions?

Well, first thing, if you do a bad job, if you don't care about your co-workers, if you're not a team player, if you keep bad mouthing someone... You'll find someone that heard about the things you do and may damage your reputation.

So be nice and a team player.

Just to be clear: Yes, I did my fair share of not being a team player and bad mouthing people1 and I'm pretty sure there are companies around that would never hire me 'cause someone inside heard that I bad mouth someone or didn't do as a team player in some other place. I try to avoid doing it so as much as I can but, hey, I'm just human.

1

I still call actions of previous colleagues around even to this day. If I'm bad mouthing or just telling what happened is up to whoever is listening to me.

Blogging About Your Stupid Solution Is Still Better Than Being Quiet

You may feel "I'm not start enough to talk about this" or "This must be so stupid I shouldn't talk about it". Don't.

Create a blog. Post about your stupid solutions. They are still smarter than someone else's solution.

Also, come back later and fight your own solutions with better ones.

Show your growth.

But do yourself a favour and turn off comments. Unfortunately, the internet is a toxic place and the fears you may have are created by a small portion of it that doesn't care about people learning.

Focus on your work. Focus on whatever you are thinking. Post about your speculations if something would work. Revisit them later. Answer yourself. All that will show that you're interested in the field and will count points towards you.

There are several options on where to blog; even Github/Gitlab can be used to blogging, using their Pages features.

Don't Hide Your Stupid Solution

You may think "This project is so small and so focused on whatever I needed, I should never post it on Github. What would people think?" Github is not for that.

Github is not a repository for "cool, almost perfect" projects. You're free to show that, at some point, you were a beginner1.

You can always come back, review what you did and fix it. It will, as your blog, show that you're improving.

... or maybe you'll let your project there just to rot. I still have some Python projects that I wrote when I was learning the language that, although they work, they don't look like Python projects.

But who knows? Maybe the code you wrote to solve your small problem can help someone else to fix their problem, which was not exactly the same, but pretty close. Or even you could get a code review that would teach you something new about the language/design you used.

1

Whoever see the first projects I did in Rust wouldn't think I have 30 years of experience in the field. Everybody is a beginner at some point.

Keep A List of Things I Don't Know

Richard Feymann, famous physicist, kept a notebook with the title "Things I Don't Know".

I keep a similar "Task List" for myself. If some technology starts appearing everywhere or something grabs my attention, but I don't have the time to research it, I put it on this task list.

When I start my research, I keep some notes together, although not on paper, so I can use as reference in the future.

You Always Have The Time

You may think "Alright, I have a list of things I don't know, but I have no time to learn those things!" You do have time.

Most of this blog/book was written during my lunch breaks; I can have my lunch in 30 minutes, and then I still have 20-30 minutes free for myself. In those lunch breaks, I wrote a very stupid application in Rust to download some stuff I wanted.

I don't fall asleep straight away, it still takes me about 30 minutes to actually feel sleepy. That's when I pick my tablet and read a book, which most of the time is technical book, about some technology I'm interested in.

Even if when I get home I don't feel like sitting in front of a computer to code/write something, I always have the time to slowly progress.

And that's how I always have the time.

Sure, I could take those 30 minutes after lunch just to play games. I could put myself to sleep watching Netflix. But, then again, I'd never wrote this bunch of words, would never have an automated downloader and would not learn about new stuff that people are talking about.

Maybe people think "If I don't finish, it's over". Your life doesn't end in one day. You still have tomorrow to keep going -- to keep going, not to start.

Own Your Shit

When I said "Scala is garbage" or "Gerrit is a mistake", it wasn't "l33th4x0r" who said that; it was Julio Biason. 'Cause I do believe that putting your face to be slapped is the way we grow.

I do understand -- and you must have realized reading some of the previous points when I talk about it -- that privacy is important. For some people, hiding their real name is important for lots of reasons.

But I also personally believe that using some weird name and some face that isn't yours on your avatar may give you a false sense of "that person is the guilty one, not me" when it comes to criticism.

I mean, yes, I hate Scala with a passion. I do understand why the language developers decided to come with some options about it, and I still think those decisions were stupid and you should never sacrifice readability for ease-to-use.

But it wasn't some random person using a weird name full of numbers and an avatar of some anime saying Scala is garbage. My name is even in this blog name, in the URL and in every social network I use there is a picture of me.

So yeah, Julio said Scala is garbage.

There is another thing about using your real name and real face: I'm not just saying "Scala is garbage", I have to back that up with some facts -- or, in this case, personal opinions -- about it. It's not simply an attack to Scala, is a somewhat thought out attack on Scala.

And, on top of that, someone will one day come to me and say "Yeah Julio, that thing you said about Scala: It is that way because of this, this and this". And I'll probably have to say "You know, you're right: I was wrong." Because I can't simply move my mistake to some other personality; who was wrong was me. And unless I own my shit up, I'd never get the understanding I'd need to see my mistake about Scala in the first place.

Don't Defend Bad Code

Bad code exists everywhere. You shouldn't defend it, even if it is your own code.

Bad code isn't bad on purpose. It sadly happens. But because it is bad, you shouldn't defend it.

For example, an application does whatever you need. But it crashes from time to time. Software shouldn't crash and you shouldn't defend it just because it does whatever you need.

Your internal application works on a single browser. That's bad. "But maybe the other devs thought it wouldn't be worth working on all browsers". No. It is bad. You shouldn't defend the other devs because they decided to focus on a single browser due whatever problems they were facing. Sure it wasn't nice that they had to do this trade-off, but it is still bad software.

If we keep defending this kind of software, we will still get bad software.