As developers, we tend to display our technical knowledge as often as we can. It feels good to showcase how much we actually do know in our specific areas and we feel really professional when we code something really clever. But the one thing we often forget is to ask a really basic question: Why? Why are we behaving like that and do we really need to do that?
The yearly challenge
Every year, to celebrate my own growth, I choose a specific challenge to see how I’ve grown in the past 12 months.
Two years ago, I spent a lot of time diving deep into JS world, so naturally, I decided to tackle the adventofcode in a specific way. I decided to solve daily challenges with JS, but following the rule of `no keywords`. It was really interesting and I got even deeper into the core definitions of JS and how things are being handled. If you are interested in how this actually looked like, you can check the source code.
Spoiler alert: I didn’t finish the challenge completely, but I did get to the point where I was really satisfied with my understanding of JS.
Last year, I was really focused on soft skills and interview processes in tech companies. Following my own growth in that area, I got into more than 30 different job interviews during the whole year. This allowed me to test my new social skills and to learn about selection process in top companies in Croatia.
I changed my approach, from one interview to the next, from being really positive to indifferent, from being punctual to late, from chatty to secluded. I solved tasks, I missed deadlines, I’ve been a junior, an intermediate and a senior. Finally, I’ve learned about the benefits and drawbacks of almost every combination of these basic behaviours.
This year, I’ve focused on frontend development and being a true problem solver, in a sense that I don’t want to talk about toolsets (frameworks, libraries, programming languages) until we “solve the problem”.
Challenges for this year:
In light of that, I gave myself two challenges this year. The first one was to solve this year’s adventofcode in 25 different programming languages, thereby testing my “problems before tools” mindset. The second: making a game using only CSS.
Making a game using only CSS
CSS is a style sheet language used for describing the presentation of a document written in a markup language like HTML.
With that in mind, it has no options to actually program stuff and do things like conditional behaviour, blocking states or looping. But, me being me and me being a developer, I started thinking how far I can take it. I’ve seen things like Tic-Tac-Toe in pure CSS and, even though it’s awfully cool, nice looking and a good deal of time went into it, it boils down to the clicking and hiding/showing things in different layers and overlapping when previous layer is checked. I liked the idea a lot, but I was missing the keyboard interaction like moving around with arrows.
But to just understand the previous example meant going over all of the basic behaviours of CSS: sibling operators, pseudo-selectors and pseudo-elements. So, I got the idea of implementing a retro racing game, something like this.
That would require moving around with arrows, which is as simple as taking two radio inputs and changing states depending on a combination of inputs, and detecting crashes.
See the Pen
Basic movement with radio buttons by Vilim Stubičan (@vilimco)
With this I had my movement, but still had to work on crash detection.
Since I’ve worked a lot on frontend development recently, I already knew much about animations, CSS variables and CSS counters. So, I was really enthusiastic to see that I can change a variable’s state with an animation.
For me, at that point, it was a huge success because now I could actually change state over time and use different variable values with `animation-play-state` to pause time propagation and actually detect crashes.
See the Pen
CSS counter with variables and animations by Vilim Stubičan (@vilimco)
So the game flow would go like this:
- split game into scenes – every boulder passing is a scene where you can be at one of the three positions,
- each scene is a simple animation where we are changing CSS variable state in following iterations: 0-15% = `[running, none]`, 15-50% = `[paused, block]`, 50-100% = `[running, none]`,
- delay animations for the scene durations; first scene is played immediately, second one 5 seconds later, third one 10 seconds later, etc.,
- if `input[type=”radio”]` on position 1 is checked when we are expecting a boulder there, we simulate the crash by using the state variable with `animation-play-state`, therefore dynamically stoping the animation on the element itself.
In my head, this was brilliant. I could detect the crash in a time period, block the further execution and have something that I could detect as the proper game end.
When I implemented this, I got confused. It wasn’t working! My state change was ignored, even though it was properly set up. This frustrated me so I started digging.
After a lot of searching and even going to the extent of reading the rendering engine’s source code, I found that not everything can be changed dynamically, especially the things I had envisioned. This finally led me to the end of that idea, but I still implemented a game with arrow movements using only CSS.
I wasn’t too satisfied with this, since I had to rely on the human psychology of “trusting the big red sign” and not doing anything besides clicking the “Start again” button.
I also had a “bug” of not being able to block further gameplay with only CSS. If you want to see the whole code and maybe play around with it, here you can find the full codebase and here you can play with it (for best experience, go into fullscreen mode and please read the instruction on how to play it).
In the previous examples, I went above and beyond with complicated solutions just to code it in a specific way, i.e. intentionally pushing the use of a specific tool. I took basic elements and pushed them to the limits and created something that is hard to follow even for me, the person who wrote it.
We as developers tend to do this really often. We understand something to the core and we want to let everyone else know we have this knowledge because we spent a lot of time getting there and it feels good to get the acknowledgment for our efforts.
The problem in that is we shouldn’t force-feed our knowledge to others. We need to be smart enough to know that we don’t have a golden hammer and that not every problem we have is a nail. We need to find the best possible solution for every problem and that won’t always be the tool we are most comfortable with. And that’s OK. ?
Simplicity over ingenuity
When approaching a problem, we have to balance a few things: the team working on the problem, the business requirements, client experience, the stage of your company’s growth and many others.
If you have a fair amount of juniors or intermediate developers, you have to keep your code and processes simple enough so that they can contribute easily.
Meaning, if you have that one rockstar ninja developer that writes code he/she and only he/she can maintain and understand, you have a problem. That person is not replaceable, which means they can never go on a vacation and they will have to handle all the bugs alone since no one else understands their software. Also, they are probably well on their way to burnout.
Business requirements are problems that we are solving for our clients, but that doesn’t mean everything actually is a problem. We have to ask our clients to determine if something actually is a problem or if it is something that they have just heard somewhere. There will be times when those two situations will overlap, but that is mostly an exception, not a rule.
Furthermore, those same clients maybe just don’t have enough experience in the kind of problem they are trying to solve. In that case, we will be the ones explaining our solutions to them. If we choose something ingenious just for the sake of being ingenious, we are going to have a hard time guiding the client through the solution. One very useful approach you can use to find out if your solution is too complex is the “5-year-old test”. If you can explain your solution to a 5-year-old child, you have a solution that is simple enough. If you can’t explain your solution to a senior member of your team with over 10 years of experience, then you have a problem.
Sometimes you, as a company, haven’t evolved enough to implement great, but complex solutions. These days, almost all backend developers are in favour of doing Domain Driven Design or CQRS due to their modularity and code cleanliness. But if you are a small agency just breaking out to the market, you want to earn money and reputation among clients so that you can build your presence and continue with your growth. Even though those solutions may seem as the better approach from the code perspective, it is not the best solution for your company at the moment.
Scaling your simplicity and complexity
At the end of the day, you have to see what works best for you and your company. Don’t fall into the trap of choosing complex solutions just because they seem like the best for one part of your business.
Scale simple solutions for as long as they are fulfilling your needs to their best possibility. When the moment comes, go for the better solution, (probably) the more complex one.
My final advice is to always start simple, with the basics. If you set those up properly, you won’t have problems with implementing more complex solutions and solving difficult problems while retaining simplicity in your company. For me personally, this means finishing my second challenge: solving the 25 programming tasks in 25 different programming languages.
Have you tried anything really complex just to prove something to yourself? Let me know at firstname.lastname@example.org.