Julio Biason
4 years ago
87 changed files with 2298 additions and 0 deletions
@ -1 +1,19 @@ |
|||||||
# Don't Defend Bad Code |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 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 |
# 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 |
# 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 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 |
# 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 |
# 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 |
# 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 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 |
# 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 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 |
# Writing code |
||||||
|
|
||||||
|
Let's put those things to work! |
||||||
|
@ -1 +1,22 @@ |
|||||||
# Don't Use Booleans As Parameters |
# 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 |
# 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 |
# 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 |
# 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 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# Source Control |
||||||
|
|
||||||
|
Programming is coding and coding needs to be stored somewhere. |
||||||
|
@ -1 +1,23 @@ |
|||||||
# Always Use A Version Control System |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# 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 |
# ... 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 |
# 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 |
# 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 |
# 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 |
# "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 |
# 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 |
# 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