Julio Biason
4 years ago
87 changed files with 2298 additions and 0 deletions
@ -1 +1,19 @@
|
||||
# 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. |
||||
|
@ -1 +1,23 @@
|
||||
# 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. |
||||
|
@ -1 +1,17 @@
|
||||
# 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. |
||||
|
@ -1 +1,21 @@
|
||||
# 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](./watch-reactions.md), 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? |
||||
|
@ -1 +1,20 @@
|
||||
# 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](../programming/running/monitoring.md) 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. |
||||
|
@ -1 +1,25 @@
|
||||
# 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. |
||||
|
@ -1 +1,45 @@
|
||||
# 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 is![^1]. |
||||
|
||||
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 |
||||
troublemaker[^3]). |
||||
|
||||
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. |
||||
|
@ -1 +1,23 @@
|
||||
# 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. |
||||
|
@ -1 +1,36 @@
|
||||
# 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. |
||||
|
@ -1 +1,12 @@
|
||||
# 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. |
||||
|
@ -1 +1,24 @@
|
||||
# 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 beginner[^1]. |
||||
|
||||
You can always come back, review what you did and fix it. It will, as your |
||||
[blog](./blogging.md), 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](https://www.rust-lang.org/) wouldn't think I have 30 years of |
||||
experience in the field. Everybody is a beginner at some point. |
||||
|
@ -1 +1,20 @@
|
||||
# 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_). |
||||
|
@ -1 +1,38 @@
|
||||
# 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](https://tusky.app/), 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](https://github.com/tuskyapp/Tusky/pull/1303), basically |
||||
[rickrolling](https://en.wikipedia.org/wiki/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](./say-no.md) 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. |
||||
|
@ -1 +1,17 @@
|
||||
# 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. |
||||
|
@ -1 +1,26 @@
|
||||
# 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 people[^1] 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. |
||||
|
@ -1 +1,33 @@
|
||||
# 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 it[^1]. 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. |
||||
|
@ -1 +1,14 @@
|
||||
# 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. |
||||
|
@ -1 +1,12 @@
|
||||
# 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](../organization/paper-notes.md), so I can use as reference in the |
||||
future. |
||||
|
@ -1 +1,15 @@
|
||||
# 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. |
||||
|
@ -1 +1,27 @@
|
||||
# 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. |
||||
|
@ -1 +1,24 @@
|
||||
# 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](./learn-about-yourself.md) and the way [people react to |
||||
you](./watch-reactions.md). |
||||
|
@ -1 +1,21 @@
|
||||
# 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. |
||||
|
@ -1 +1,46 @@
|
||||
# Cognitive Cost Is The Readability Killer |
||||
|
||||
"[Cognitive dissonance](https://en.wikipedia.org/wiki/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: |
||||
|
||||
```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](../coding/data-types.md). Also, this may |
||||
sound a bit like [the magical number seven](./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. |
||||
|
@ -1 +1,25 @@
|
||||
# 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. |
||||
|
@ -1 +1,30 @@
|
||||
# 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](./crash-it) '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](./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. |
||||
|
@ -1 +1,58 @@
|
||||
# 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 number[^1]. An example of a |
||||
pure function would be something like this in Python: |
||||
|
||||
```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](./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. |
||||
|
@ -1 +1,99 @@
|
||||
# The Magic Number Seven, Plus Or Minus Two |
||||
|
||||
"[The magical number](https://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two)" |
||||
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. |
||||
|
@ -1 +1,30 @@
|
||||
# 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](http://spring.io/) and |
||||
[Serde](https://serde.rs/) '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. |
||||
|
@ -1 +1,25 @@
|
||||
# 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. |
||||
|
@ -1 +1,3 @@
|
||||
# Writing code |
||||
|
||||
Let's put those things to work! |
||||
|
@ -1 +1,22 @@
|
||||
# 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. |
||||
|
@ -1 +1,41 @@
|
||||
# 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: |
||||
|
||||
```java |
||||
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. |
||||
|
@ -1 +1,31 @@
|
||||
# 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. |
||||
|
@ -1 +1,19 @@
|
||||
# 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". |
||||
|
@ -1 +1,27 @@
|
||||
# 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](./crash-it.md): |
||||
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. |
||||
|
@ -1 +1,24 @@
|
||||
# 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](./boolean-parameters.md), 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](../testing/tests-apis.md), |
||||
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.) |
||||
|
@ -1 +1,28 @@
|
||||
# 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](./data-types.md), 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. |
||||
|
@ -1 +1,33 @@
|
||||
# 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. |
||||
|
@ -1 +1,31 @@
|
||||
# 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](../before/spec-first.md), you may have a perfect |
||||
understanding of the whole [in your Gherkin |
||||
files](../before/gherkin.md) 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. |
||||
|
@ -1 +1,21 @@
|
||||
# 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](../testing/tests-in-the-command-line.md) 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. |
||||
|
@ -1 +1,32 @@
|
||||
# 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. |
||||
|
@ -1 +1,24 @@
|
||||
# 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. |
||||
|
@ -1 +1,32 @@
|
||||
# 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](../before/spec-first.md) 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](../testing/integration-tests.md). |
||||
|
@ -1 +1,19 @@
|
||||
# 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. |
||||
|
@ -1 +1,62 @@
|
||||
# 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: |
||||
|
||||
```python |
||||
a_tuple = ('A String', 1, 7.5) |
||||
``` |
||||
|
||||
And you can destructure it with |
||||
|
||||
```python |
||||
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](https://docs.python.org/3/library/collections.html?highlight=namedtuple#collections.namedtuple), |
||||
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? |
||||
|
@ -1 +1,31 @@
|
||||
# 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". |
||||
|
@ -1 +1,45 @@
|
||||
# Always Use UTF-8 For Your Strings |
||||
|
||||
Long gone are the days where [ASCII](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/Unicode) and |
||||
[uTF-8](https://en.wikipedia.org/wiki/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. |
||||
|
@ -1 +1,3 @@
|
||||
# Documenting your code |
||||
|
||||
What does this piece of code do? Is "self-documenting code" actually real? |
||||
|
@ -1 +1,23 @@
|
||||
# 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](./document-is-contract.md)), 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](https://en.wikipedia.org/wiki/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](../programming/functional-programming.md). |
||||
|
@ -1 +1,29 @@
|
||||
# The Function Documentation Is Its Contract |
||||
|
||||
When you start the code by [writing the general flow as |
||||
steps](../programming/steps-as-comments.md) 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. |
||||
|
||||
<!-- more --> |
||||
|
||||
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](./document-id.md) 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. |
||||
|
@ -1 +1,37 @@
|
||||
# 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... :( |
||||
|
@ -1 +1,29 @@
|
||||
# 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](../testing/languages-tests.md): 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. |
||||
|
||||
|
@ -1 +1,4 @@
|
||||
# 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? |
||||
|
@ -1 +1,38 @@
|
||||
# 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](../programming/future-trashing.md). What you can do is actually |
||||
break your project by functionality, [making modules related to their |
||||
data](./project-organization.md) 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. |
||||
|
@ -1 +1,14 @@
|
||||
# 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! |
||||
|
@ -1 +1,74 @@
|
||||
# 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. |
||||
|
@ -1 +1,4 @@
|
||||
# Making Things Go |
||||
|
||||
How to make things easier for you when you already have the application in |
||||
order? |
||||
|
@ -1 +1,23 @@
|
||||
# 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. |
||||
|
@ -1 +1,20 @@
|
||||
# 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](./command-line-options.md). 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. |
||||
|
@ -1 +1,34 @@
|
||||
# Not Just Function Composition, But Application Composition |
||||
|
||||
When we were discussing [the magical number |
||||
seven](../before/magical-number-seven.md), 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](./config-file.md) 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. |
||||
|
@ -1 +1,22 @@
|
||||
# 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](https://en.wikipedia.org/wiki/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. |
||||
|
@ -1 +1,45 @@
|
||||
# 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](../programming/libraries.md) 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 while[^1]; 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](https://en.wikipedia.org/wiki/Twelve-Factor_App_methodology), |
||||
you'll find that you're half-way through it. |
||||
|
||||
[^1]: In other words, they have a [time to |
||||
live](https://en.wikipedia.org/wiki/Time_to_live). |
||||
|
@ -1 +1,36 @@
|
||||
# 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. |
||||
|
@ -1 +1,19 @@
|
||||
# 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. |
||||
|
@ -1 +1,20 @@
|
||||
# Be Transparent With The User |
||||
|
||||
Since we are talking about [logging](./log-events.md), |
||||
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](https://joplinapp.org/), 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. |
||||
|
@ -1 +1,3 @@
|
||||
# Source Control |
||||
|
||||
Programming is coding and coding needs to be stored somewhere. |
||||
|
@ -1 +1,23 @@
|
||||
# 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. |
||||
|
@ -1 +1,26 @@
|
||||
# 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. |
||||
|
@ -1 +1,19 @@
|
||||
# Git-Flow Is The Way To Go |
||||
|
||||
If [Gerrit is such a mistake](./gerrit.md), 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! |
||||
|
@ -1 +1,29 @@
|
||||
# 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 field[^1]. |
||||
|
||||
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? |
||||
|
@ -1 +1,4 @@
|
||||
# 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? |
||||
|
@ -1 +1,65 @@
|
||||
# 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](https://spring.io/)+[Mockito](https://site.mockito.org/) 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](https://en.wikipedia.org/wiki/Daemon_(computing)) 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](../programming/gherkin.md) tests, although I didn't |
||||
know Gherkin at the time -- and, better yet, we had tests that proved that we |
||||
were following the [spec](../programming/spec-first.md). |
||||
|
||||
Personally, I think over time integration tests become more important than |
||||
unit tests. The reason is that I personally have the feeling[^2] 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. |
||||
|
@ -1 +1,15 @@
|
||||
# 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. |
||||
|
@ -1 +1,38 @@
|
||||
# Tests Make Better APIs |
||||
|
||||
Testing things in isolation may give a better view of your APIs. |
||||
|
||||
After reading the [integration tests](./integration-tests.md) chapter, you may |
||||
end up with the impression that I don't like unit tests[^1]. |
||||
|
||||
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 layers[^2] -- 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](https://en.wikipedia.org/wiki/Turtles_all_the_way_down). |
||||
|
@ -1 +1,50 @@
|
||||
# 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](./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. |
||||
|
@ -1 +1,27 @@
|
||||
# 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](https://en.wikipedia.org/wiki/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. |
||||
|
@ -1 +1,31 @@
|
||||
# Understand And Stay Away From Cargo Cult |
||||
|
||||
"[Cargo cult](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/Martin_Fowler_(software_engineer)) 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](./languages-are-more.md) than just its |
||||
development. |
||||
|
@ -1 +1,39 @@
|
||||
# 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. |
||||
|
@ -1 +1,22 @@
|
||||
# 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. |
||||
|
@ -1 +1,24 @@
|
||||
# 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. |
||||
|
||||
<!-- more --> |
||||
|
||||
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](./languages-are-more.md) 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](https://en.wikipedia.org/wiki/Indentation_style#K&R_style); if you're |
||||
working with Python, there is only one style: |
||||
[PEP8](https://www.python.org/dev/peps/pep-0008/). |
||||
|
@ -1 +1,9 @@
|
||||
# ... 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. |
||||
|
@ -1 +1,21 @@
|
||||
# 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](./right-tool-obvious.md). Don't let that make you |
||||
feel down. |
||||
|
@ -1 +1,4 @@
|
||||
# Community/Teams |
||||
|
||||
Programming is barely a solo endeavour. You'll have to deal with more people |
||||
when working on your projects. |
||||
|
@ -1 +1,47 @@
|
||||
# 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 |
||||
problems[^1]. |
||||
|
||||
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 own[^2]. |
||||
|
||||
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. |
||||
|
@ -1 +1,14 @@
|
||||
# "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". |
||||
|
@ -1 +1,19 @@
|
||||
# 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. |
||||
|
@ -1 +1,32 @@
|
||||
# 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](https://en.wikipedia.org/wiki/Fuzzing) 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. |
||||
|
Loading…
Reference in new issue