loading...
Cover image for Improving your (Web) Dev Foo

Improving your (Web) Dev Foo

puritanic profile image Darkø Tasevski ・12 min read

I've been writing this since the last year, and in the end, I wasn't sure if I should publish it as this is mostly just a rant. Hopefully, some of you might find something interesting here as I wrote up some of the stuff I've learned and I'm doing in practice to keep writing effective and clean code.

Editor/IDE

If you are starting learning about web development, you can't go wrong, pick any code editor and code on.

Currently, for web development, there are many choices when you come to choosing the editor for your work. I use Webstorm/Xcode combo for work and VS Code/Vim for my stuff. From my experience, my suggestion would be that beginner devs start learning with a simple editor without many plugins, such as VS Code, Notepad ++ or Sublime Text, typing out all those keywords and language methods by hand helps a lot with memorizing/learning all that stuff. Once you feel comfortable with a language you're writing your code with, you can switch to full-blown IDE like Webstorm or plugins enhanced VS Code.

Linters & Formatters

TLDR: Use Eslint and Prettier

When your code base gets bigger, it's also more challenging to keep an eye on all those lines, and syntax errors problems creep in. By highlighting problematic code, undeclared variables, or missing imports, your productivity can be increased a lot and is going to save a lot of time and many nerves as well.

Using Eslint from a very start would also help a lot with learning Js, as it will force you to build healthy habits and write clean code. Over the years, I've tailored my version of the eslint rules based on eslint-config-airbnb, but lately, I've been looking into Wes Bos's eslint config and would probably give it a go in a some of my future projects.

Beside Eslint I'm using Prettier for code formatting, combined with husky and lint-staged for automating linting/formatting as precommit hooks.

The optimal directory structure

Dan Abramov's solution

I have mixed feelings about this topic. The only thing that I'm sure of is that there is no right solution.
Every application is different in some way or another, and each project has its own distinct needs. How we structure our applications should change based on the needs of the project, just like the technologies we choose.

Do not try to optimize the project structure from the beginning of the project, but keep in mind that changing the project structure later in the project can be a problem in VCS because of history rewriting.

That being said, don't overthink it. Pick a folder structure that works for your application. If you’re spending a massive amount of time organizing and reorganizing components, containers, styles, reducers, sagas, you’re doing it wrong.


File naming

Rant incoming

Regarding file naming, one rule that I find immensely useful is this: name your file the same as the thing you’re exporting from that file. I can't stress enough how angry I feel when I have hundreds on index.js files in a poorly structured project, and finding some chunk of logic takes so much time, which feels wasted...

It staggers me that some people are happy to work like this.

indexes everywhere

Even if your IDE is clever and puts the directory in the tab name for non-unique filenames, you still have a bunch of redundancy there, and will run out of tab room sooner, and you still can’t type the filename to open it. Having said that, I understand that there is the reasoning behind this — It’s a definite trade-off. Shorter import statements vs. file names that match exports.

Not a worthy trade-off, in my opinion.

In my case, for the last two or three years, I'm mostly using CRA's project structure, with a few modifications, like adding a redux/ or sagas/ dir for state management logic and moving all jsx/tsx files to components/.

Debugging

Just console.log anywhere where you think there is a code "smell."

Writing about debugging can be a post on its own, yet there is a lot of already excellent posts and courses on Js debugging so I'll keep it short.

Many devs would say that using debugger looks more professional, but the console.log is something I'm using the most for a quick debugging. I'm lately working on the apps for Smart TVs and streaming devices, and those are not really debugger friendly, so logging data in console or going through device logs in telnet is sometimes the only way to debug. That aside, you should learn how to use the debugger, as it can save you with more complex bugs.

The simplest way to debug, at least in terms of tooling, is by reading the code you wrote. After that, use the console.logs, and if even that doesn't pinpoint the problem, switch to the debugger and good luck.

Documentation

Don't write a comment just for the sake of it. Write comments only for stuff that needs explanation.

We all (hopefully) know how important proper documentation and reference material is to a successful software project. Without good docs, a particular library may be next to impossible to use. Without a reference to how different components and methods work in isolation, let alone examples of how all the different pieces of a project fit together with each other, we are left to interpret the original intention of the author merely by reading the source code, or if we are lucky, reaching for StackOverflow and googling random error messages. If this is an in-house or small project, you are probably entirely screwed (been there).

This is especially important if you are working with several other fellow devs on the project; think about what the other member of the team is going to think about that hack you wrote when he doesn't know why is that needed. By keeping knowledge of how the code works and why is there many hacks in it or intentionally making code more complicated than it needs to be, you are just making lives of the everyone working on the same project more harder. And if you are doing this for the sole purpose of assuring your job security, you are just a censored.

For documenting my projects, I've been using JSDoc syntax. JSDoc is a standardized way of writing comments in your code to describe functions, classes, methods, and variables in your codebase. The idea is that we describe how our code works with a few special keywords and formatting conventions, and later we can use a parser to run through all of our commented code and generate beautiful, readable documentation based on the comments we write.

Here’s a short example of how's it looking like in practice:

/**
 * @desc Represents a book.
 * @param {string} title - The title of the book.
 * @param {string} author - The author of the book.
 */
function Book(title, author) {
}

Some of this stuff can be replaced with Typescript types, but even with that, for more complex functions, it's helpful if we have a good explanation of what it is doing and why do we need it to do that.

Also, every and one hack in your code should be documented, believe me, future you are going to be thankful for that.

And for the end, if you already haven't, please read Clean-Code by Robert C. Martin. Writing clean and readable code is a skill on its own.

Learn Javascript

But seriously

Jumping on a Js framework or using a library to solve problems you have because you're not familiar with the core language is going to bite you soon enough. But do not despair, most of us have been there on some level, Js documentation is enormous and, unless you have an excellent memory, there is no way to memorize even a quarter of this stuff. But leveraging Pareto principle (also known as the 80/20 rule) would be in many cases just enough. Learn how is this working, all possible operations you can do with objects and arrays, that in Js everything is an object, scope rules, async ops, prototypes (you'll rarely use these, but it's essential to understand how inheritance in Js works) and coercion scenarios (so you can laugh at people making stupid mistakes by adding numbers to strings or arrays to arrays and then creating posts on Reddit flaming Js).

There is truth in saying that if you know Javascript good, then all frameworks and tools based on it are going to be much easier to learn. In the end, those are all just Js under the hood.

I can also recommend reading You Don't Know JS book series if you want to dive deep into Js core mechanisms. (I'm rereading it for the 2nd time).

Use the latest standards

Keep up with times

It can be challenging to keep up with all the things going on in the web development world, especially since the JavaScript language itself has been changing a lot over the last several years. In 2015, TC39, the committee responsible for drafting the ECMAScript specifications decided to move to a yearly model for defining new standards, where new features would be added as they were approved, rather than drafting complete planned out specs that would only be finalized when all features were ready. As a result, we have ES6 - ES10 specifications which have changed the language a lot, and in a better way. Each of these specifications includes a set of new features/improvements integrated into Javascript, nullifying the need for cumbersome libraries or tools so that you can work with Js and not pull your hair out.

If you want to get a quick overview of the features being considered for future releases, the best place to look is the TC39 proposals repo on Github. Proposals go through a 4 stage process, where stage 1 can best be understood as a cool “idea,” and stage 4 is “confirmed for the next ECMAScript release.” There is a lot of cool stuff coming with ES10 :)

If you’re interested in keeping up with new proposals but want somebody to walk you through them, I recommend subscribing to Axel Rauschmayer’s 2ality blog. But if you are more of a social interaction person, probably the easiest way to keep up with language evolution is to follow the people who are shaping and teaching the new language features: @TC39, Sebastian Markbåge, Mathias Bynens, Daniel Ehrenberg, Tierney Cyren, Axel Rauschmayer and probably a lot of other people I forgot.

Babel has implemented almost all of the higher stage proposals on the TC39 list, and you can try them out in the Babel REPL or by setting up a small project that loads in Babel with the appropriate plugins installed. Also, ff you aren’t familiar with ES6 yet, Babel has a excellent summary of its most essential features.

Typescript

Types good, boilerplate code bad

JavaScript is a loosely typed language, also known as a dynamically typed language, which means that it is flexible and does type checking on run-time rather than compile time. This means you can create a variable that starts as string type, but then change it to a number type, etc.
For many people that have started programming in C or Java, this is horrifying (ergo coercion memes on Reddit), as those languages are pretty strict with types and require a full definition of data type or interface for a constant. Anyway, there’s a lot to love about static types: static types can be beneficial to help document functions, clarify usage, and reduce cognitive overhead. But, IMO, there’s a lot to love about dynamic types as well.

So, there comes Typescript. Typescript is Javascript, with an extra layer that adds static typing tools and capabilities to your Javascript code. As you’re developing an application, you will be writing Typescript code, which then gets compiled to plain JavaScript for the browser to understand. It can fix some of the issues dynamically typed languages have, a big plus is if you use one of the TS supported editors (VS Code, Atom, Webstorm) which can provide the excellent dev experience and boost your productivity. That aside, I hate a boilerplate code that comes with TS. A few TS projects I've been working with have at least 30-40% more lines of code just because of TS types, interfaces and enums. Errors can be cryptic sometimes, and debugging type issues can get on a nerve real quick. Merged types and some advanced type definitions can be tiring to read and understand sometimes.

There is a excellent article by Eric Elliott about Typescript's bad and good sides if you want to read more. Still, my overall opinion of TS is positive, just because whenever I go back to read the code, I can (almost always!) understand immediately and thoroughly what each type of variable is, what this function returns, whether this array has been changed throughout the program, etc. That's a lot of saved time and minimized the number of redundant operations to check the type and structure of the data.

Code testing, integration, and delivery

Give a developer a tedious task, and they'll find a way to automate it

Probably most of us here are familiar with tools as Webpack, Gulp, Grunt, lint-staged. Even Prettier and Eslint are a kind of automation tool. The less time we spend on menial or repeating tasks, the more time we have to focus on the actual problems.

Few developers get excited over the idea of writing tests for their code. Especially when there is a pressure to finish new features as fast as possible, writing test code that doesn’t directly contribute to the progress of the project can be annoying. When the project is small and you can test a few available features manually this might be fine, but once the project starts to grow manual testing is time-consuming and horribly inefficient.

Investing in testing upfront is one of the best investments you can make on your project. It is what allows you to write a feature, not touch it for weeks, come back, see it is passing all its tests, and have a level of confidence that everything is good in the world.

I've been using mostly Jest for my tests, but I've heard good things about Riteway. Testing React components have gotten more difficult since the introduction of the hooks, Enzyme is having a hard time so I'm not sure if I can recommend it anymore, react-testing-library might be a better choice for now.

Continuous integration is the software development practice of frequently integrating changes to a shared code repository. For every integration, automatic formatting and testing should be done. This gives the developers a quick feedback cycle for determining potential conflicts in commits while also allowing to frequently merge new updates to an application.

Continuous delivery works in conjunction with CI to take the tested and built application that results from the CI process and deploy (or deliver) it to the intended infrastructure. With CD, teams can push new code to production every day or even hourly and get quick feedback on what users care about.

A lot can be told about how to write tests and how to configure CI/CD pipeline but that would be a whole post on its own. There is no golden rule for how to write perfect tests but making sure that you at least write them and are trying to get ~80% coverage with a combination of unit, integration, and e2e tests should lead to clean and confident code.


Summary

I always struggle with summaries (same thing with prefaces). For me, it's usually hard to start writing a post, after that, I can go on and on, same with deciding how to end it 😄 I still feel like I haven't wrote enough about all topics mentioned, so feel free to comment if you have any questions.

Bear in mind that this is a half rant and half commentary to myself, after several years working with Js. There’s a whole class of internet comments that can be summarised as “I disagree, and that makes me ANGRY, here's a downvote”, which is a pity, because when two reasonable people disagree, there’s very often something interesting going on.

Thanks for reading!

Photo by Adi Goldstein on Unsplash

Posted on by:

Discussion

markdown guide
 

Good one:

"The simplest way to debug, at least in terms of tooling, is by reading the code you wrote. After that, use the console.log."

Totally resonates with me. I know that every 'expert' recommends using a debugger and not write "console.log" but somehow console.log does the trick for me most of the time. But yes for complex bugs, by all means use a debugger.

And:

"Testing React components has gotten more difficult since the introduction of the hooks"

This surprised me, I thought hooks (and the "functional component" approach) was supposed to make testing easier compared to conventional class based React components.

 

This surprised me, I thought hooks (and the "functional component" approach) was supposed to make testing easier compared to conventional class based React components.

We have around 2k tests in one of the projects, Jest/Enzyme combo, and I've noticed that since the introduction of the hooks, you have to jump through a few more hoops if you want the component completely covered. This is particularly true for components with a lot of async/graphql logic.

While it might be possible that I just haven't got used to a new way of testing, I just don't like that sometimes writing the tests for some logic takes more time than it was used to write that logic.

 

Right I see, wow 2K tests, that's pretty huge and impressive ... so apart from the testing story, do you think that hooks as such are an improvement over "the old way" ?

I would still use the old class-based way for the components that heavily relly on state management because a component can get crowded with useEffects and useState real quick if there is a lot of logic needed there. Keeping in mind that sometimes we need to take care of the order of the useEffect hooks, it can be really hard to refactor and change code comfortably (but tests can help with that a bit).

For everything else, I would use hooks instead of converting a component to a class, they are definitely an improvement and I'm excited to see what other exotic stuff would React team show us this year :)

Okay, so you say:

"a component can get crowded with useEffects and useState real quick if there is a lot of logic needed there. Keeping in mind that sometimes we need to take care of the order of the useEffect hooks, it can be really hard to refactor and change code comfortably"

It sounds that hooks have a lot of drawbacks then ... however next you say "they are definitely an improvement" - why are they an improvement then?

I've read a number of articles about hooks and while the basic principles aren't that hard to understand a lot of these articles then quickly descend into cases where stuff does get tricky with hooks. It made me wonder then what all the fuss is about, when class based components were in fact pretty easy to understand.

Somehow it seems down to an ideology that FP and 'pure' components are the only true way, and that classes and OO in JS (or at least in React) are something evil, at least that's the impression I get.

Well, that is the only drawback I've noticed, the complexity of the component is increasing exponentially with every new useEffect added. There is a lot of stuff to keep in mind when working with hooks, the order of useEffects is one and you have to use useRef for variables inside the component to prevent rerenders. Kent Dodds has a few short "courses" on egghead with some advanced hook techniques but those are rarely needed. In most cases, it's much better to break down the component into a few other smaller components when you start having trouble with understanding the logic.

They are improvement in a way that we don't have to switch to a class-based component to use a state, we can have lifecycles in functional components and it's really easy to cache data and methods with useMemo and useCallback. IMO, classes are far from evil and they still have their own place in React, but, from what I've heard, transpiling them into pre ES5 code has a lot of overhead code, and there are sure a few other reasons why the functions are better.

I also wholeheartedly agree that classes are much easier to understand, which is why I recommend that components/containers with a lot of state and lifecycle dependent logic should remain class-based. And, again, for everything else, function components are a better choice.

Thanks for elaborating!

Strange enough, most of what you say seems to confirm that in fact class based components are easier to understand, and that hooks can get tricky - however, then you surprisingly conclude by saying "for everything else, function components are a better choice" - but not really explaining why they are a better choice.

So the mystery for me remains why everyone is jumping on the hooks bandwagon, the only reason I can come up with being an aversion against "OO" (classes) and a love for "FP" (function components).

Can I make a guess at what would be the real reason, or a good motive, for favoring function components and hooks?

My theory is that, yes, class based components are way easier to understand when you have a complex component where you need to manage a lot of state, however the point is that you should not create complex components where you manage a lot of state - you should break them up into smaller (and potentially reusable) components, and hooks (and function components) encourage this.

So I think that hooks only pay off when you embrace a fundamentally different way of thinking and , if you don't do that and you prefer to stick with larger "monolithic" components then you're better off just sticking with classes (this is in fact also what you're hinting at).

you should not create complex components where you manage a lot of state - you should break them up into smaller (and potentially reusable) components, and hooks (and function components) encourage this.

This is basically what I wanted to say above 😄 But in a case where you can't avoid a lot of state in a parent component, it's better to let it be a class and all their children to be functions (with hooks if needed). One scenario that comes to mind is when you are expecting to work with some data from Redux which is then needed in children components, it's maybe better, in my opinion, to have class component connected to redux instead of using redux hooks in children. This has some problems on its own of course, as we need to take care not to cause unnecessary re-renders when the parent state or redux state is updated.

Just to be clear, I have nothing against OOP nor FP 😄 I'm using both paradigms in my code, as each one has its own use case.

Right, yes all of that makes sense ... sometimes you naturally just need a component that manages a larger number of fields and breaking it down doesn't buy you anything. And replacing that with two dozen "useState" calls doesn't look like that much of an improvement over doing it in the conventional way.

Your remark about Redux is spot on, I came across two articles explaining the issues when replacing Redux with its "connect" API with hooks:

staleclosures.dev/from-redux-to-ho...

Quotes - regarding when you want to move from Redux to the "Context" API with hooks (useContext / useReducer):

"Without relying on Redux you lose out-of-the-box performance optimizations ..."
"Optimizing rendering performance is now your job"

And:

itnext.io/how-existing-redux-patte...

Quote:

"When you move away from connect you lose a lot of the performance benefits it provides. This means that you’ll have to be more cautious when considering re-renders and passing data from smart and dumb components"

(core issue here: "connect" does some smart things that result in less re-rendering out of the box and which you would have to manage yourself with hooks)

By the way I see now that these are really 2 different topics, the first article is about replacing Redux with useContext/useReducer, the second is about replacing Redux 'connect' with the new Redux 'hook' API (useStore, useSelector, useDispatch and so on)

But the conclusion with either was that you had to to put in extra effort to get the performance which Redux with hooks gives you 'out of the box'.

Anyway, the pattern where you have a "master" (class) component with Redux/connect and then a bunch of stateless child components (with hooks if needed) probably makes sense. It's almost as if the master/parent component is the "controller" from the classical MVC paradigm? :-)