13 A culture of quality

This chapter covers

  • How types complement your tests and make your code safer
  • The impact of code reviews and how to perform them effectively
  • Adopting linting and formatting and the advantages of doing so
  • Setting up monitoring to ensure your systems are healthy
  • How documentation affects your project’s quality

After 12 chapters on JavaScript testing, the 13th takes a little bit of a different direction. In this chapter, I will teach you new techniques that will complement your tests and help you foster a culture of quality in your projects.

These techniques amplify the impact of your tests. They make your tests safer, your code easier to understand, or catch errors that your tests wouldn’t be able to.

This chapter starts by demonstrating how type systems complement your tests. In this first section, I talk about the advantages of adopting type systems and use a practical example to elucidate what you must do to get the most significant safety benefits out of them.

Once I’ve covered type systems, I will highlight how important it is for team members to review each other’s code and how to fit this practice into your development process. Furthermore, this section contains plenty of advice on how to review code effectively.

To ease code reviews and help developers focus on code semantics instead of nitpicking, this chapter’s third section describes linting and formatting. It clarifies the difference between these two practices and reveals the benefits of each.

The penultimate section of the chapter explains how monitoring helps to keep your software healthy, to indicate what updates your software needs, and to detect bugs you couldn’t catch in your tests.

Finally, this chapter’s last section talks about something everyone consumes but very few produce: documentation. This section covers the impact documentation can have on your team’s software and processes, which kinds of documentation to prioritize, and which not to write at all.

Because this book focuses on tests, I won’t go into too much detail in each of these sections. My main goal with this chapter is for you to understand how each of these practices and techniques can help you create better software.

After reading this chapter, I expect you to have a good idea of what content you’ll seek and how it fits into the bigger picture of software testing.

13.1 Using type systems to make invalid states unrepresentable

I think of tests as experiments that confirm your hypotheses about how your programs work. When you write a test, you have a hypothesis about what your code will do, so you give it some input and check whether the code under test yields the expected output.

Once you’ve run these experiments, you extrapolate and choose to believe the program will work in the same way in the future, even though that may not be true. It may be the case that you didn’t take a few edge cases into account or that there are other factors to play that change the code’s behavior, such as time zones if you’re dealing with time.

As we’ve seen previously, tests can’t prove a program works. They can only prove it doesn’t.

Using type systems, on the other hand, can prove properties about your programs. If you use types to specify that a function can receive only numbers, your type checker will warn you if it’s possible for that function, in any circumstance, to be called with a string, for example.

Unlike tests, type systems are not based on experimentation. They’re based on clear, logical rules with which your programs need to comply to be considered valid.

Assume, for example, that you have a function that pushes an order to the bakery’s delivery queue. Because for orders to be delivered they need to be complete, this function should add to the delivery queue only orders whose status is done.

Listing 13.1 orderQueue.js

const state = {
  deliveries: []
};
 
const addToDeliveryQueue = order => {              
  if (order.status !== "done") {
    throw new Error("Can't add unfinished orders to the delivery queue.");
  }
  state.deliveries.push(order);
};
 
module.exports = { state, addToDeliveryQueue };

Adds orders to the delivery queue only if their status is “done”

If you were to test this function, you’d probably write a test to ensure that orders with an in progress status can’t be added to the queue, as shown in the next code excerpt.

Listing 13.2 orderQueue.spec.js

const { state, addToDeliveryQueue } = require("./orderQueue");
 
test("adding unfinished orders to the queue", () => {            
  state.deliveries = [];
  const newOrder = {
    items: ["cheesecake"],
    status: "in progress"
  };
  expect(() => addToDeliveryQueue(newOrder)).toThrow();
  expect(state.deliveries).toEqual([]);
});

A test to ensure addToDeliveryQueue throws an error when someone tries to add to the delivery queue an order whose status is “in progress”

The problem with relying exclusively on tests to assert on your program’s quality is that, because of JavaScript’s dynamic nature, many possible inputs could cause your program’s state to become invalid. In this case, for example, someone could add to the delivery queue orders with zero or null items whose status is done.

Additionally, there could be other functions that update the state within orderQueue.js, which could lead to invalid states. Or, even worse, someone could try to submit a null order, which would cause your program to throw an error when checking if the order’s status is null.

For you to cover these edge cases, you’d need plenty of tests, and even then, you’d certainly not have covered all the possible scenarios that could lead to invalid states.

To constrain your program so that its state must be valid, you can use a type system.

Personally, TypeScript’s type system is my favorite. It’s flexible and easy to learn, and its tooling and community are excellent, which is why I’ve chosen it to write the examples in this section.

Before you start using types to constrain our program’s state, install TypeScript as a dev dependency using npm install -save typescript. Once you’ve installed TypeScript, run ./node_modules/.bin/tsc --init to create an initial TypeScript configuration file, called tsconfig.json. Finally, you’ll also need to change your file’s extensions to .ts. After creating that file, you’re ready to start using types to constrain your programs.

Try, for example, creating a type that represents an order and assigning a type to your program’s state. Then, update the addToDeliveryQueue function so that it accepts only orders that match the Order type.

Listing 13.3 orderQueue.ts

export type Order = {                                               
  status: "in progress" | "done";
  items: Array<string>;
};
 
export type DeliverySystemState = { deliveries: Array<Order> };     
 
export const state: DeliverySystemState = { deliveries: [] };       
 
export const addToDeliveryQueue = (order: Order) => {
  if (order.status !== "done") {
    throw new Error("Can't add unfinished orders to the delivery queue.");
  }
  state.deliveries.push(order);
};

Defines a type for Order whose status can be either “in progress” or “done” and whose items are represented by an array of strings

A type that represents the state of the delivery system that contains a deliveries property that is an array of orders

The delivery system’s state, which initially contains an empty array of deliveries

NOTE When using TypeScript, you can use the ES import syntax because you’ll use the TypeScript Compiler to translate your programs to plain JavaScript files.

Just with these two types, you have now guaranteed that TypeScript will warn you if there’s anywhere in your code that could add to the delivery queue anything other than a valid Order.

Try, for example, calling addToDeliveryQueue and passing a string as an argument to it. Then, run the TypeScript compiler with ./node_modules/.bin/tsc ./orderQueue.ts, and you will see that your program won’t compile.

Listing 13.4 orderQueue.ts

// ...
 
// ERROR: Argument of type 'string' is not assignable to parameter of type 'Order'.
addToDeliveryQueue(null);

You can go even further and specify that any order must have at least one item in it.

Listing 13.5 orderQueue.ts

export type OrderItems = { 0: string } & Array<string>    
 
export type Order = {
  status: "in progress" | "done";
  items: OrderItems;                                      
};
 
export const state: { deliveries: Array<Order> } = {
  deliveries: []
};
 
export const addToDeliveryQueue = (order: Order) => {
  if (order.status !== "done") {
    throw new Error("Can't add unfinished orders to the delivery queue.");
  }
  state.deliveries.push(order);
};

Defines that values whose type is OrderItems must have their first index filled with a string

Declares that the items property has an OrderItems type that prevents the programmer from assigning empty arrays to it

This update guarantees that it won’t be possible for the program to add to the delivery queue orders whose items array is empty.

Listing 13.6 orderQueue.ts

// ...
 
//
      ERROR: Property '0' is missing in type '[]' but required in type '{ 0: string; }'.
addToDeliveryQueue({ status: "done", items: [] })

Finally, to reduce the number of tests you’d have to write, you can also update your program’s types to guarantee that addToDeliveryQueue can accept only orders whose status is done.

Listing 13.7 orderQueue.ts

export type OrderItems = { 0: string } & Array<string>;
 
export type Order = {
  status: "in progress" | "done";
  items: OrderItems;
};
 
export type DoneOrder = Order & { status: "done" };                
 
export const state: { deliveries: Array<Order> } = {
  deliveries: []
};
 
export const addToDeliveryQueue = (order: DoneOrder) => {
  if (order.status !== "done") {
    throw new Error("Can't add unfinished orders to the delivery queue.");
  }
  state.deliveries.push(order);
};

Creates a new type that represents exclusively orders whose status is “done”

Now your program won’t compile if there’s any possibility for any place in your code to add an incomplete order to the delivery queue.

Listing 13.8 orderQueue.ts

// ...
 
// ERROR: Type '"in progress"' is not assignable to type '"done"'.
addToDeliveryQueue({
  status: "done",
  items: ["cheesecake"]
});

Thanks to your types, you won’t need the error handling within your function anymore or the test for it. Because you’ve written strict types, your program won’t even compile if you try to add an invalid order to the delivery queue.

Listing 13.9 orderQueue.ts

export type OrderItems = { 0: string } & Array<string>;
 
export type Order = {
  status: "in progress" | "done";
  items: OrderItems;
};
 
export type DoneOrder = Order & { status: "done" };
 
export const state: { deliveries: Array<Order> } = {
  deliveries: []
};
 
export const addToDeliveryQueue = (order: DoneOrder) => {             
  state.deliveries.push(order);
};

A function whose argument’s type is DoneOrder, which prevents others from calling it with any orders whose status is different from “done”

After these changes, the only test you’ll need is one that checks whether addToDeliveryQueue adds complete items to the delivery queue.

Listing 13.10 orderQueue.spec.ts

import { state, addToDeliveryQueue, DoneOrder } from "./orderQueue";
 
test("adding finished items to the queue", () => {                     
  state.deliveries = [];
  const newOrder: DoneOrder = {
 
    items: ["cheesecake"],
    status: "done"
  };
  addToDeliveryQueue(newOrder);
  expect(state.deliveries).toEqual([newOrder]);
});

A test that adds to the delivery queue an order whose status is “done” and expects the order queue to contain the new order

NOTE Before you can compile this test, you will need to install the type definitions for Jest using npm install @types/jest.

Now try using ./node_modules/.bin/tsc ./*.ts to compile all your .ts files to plain JavaScript, and then run your test with Jest to confirm the test passes.

By using types, you have constrained your program enough so that invalid states become unrepresentable. These types helped you cover more edge cases without having to write tests because TypeScript will warn you if you ever write code that does not comply with the types it expects. Furthermore, TypeScript does that without ever having to run your program. Instead, TypeScript analyzes the program statically.

On top of all that, a type system also helps you make fewer mistakes when writing tests because it will also give you warnings if your tests could lead your program to invalid states (considering your types are strict enough to allow for that to happen).

NOTE Because this is a book focused on tests, I haven’t gone too deep into TypeScript itself. If you’d like to learn more about it, I’d highly recommend Marius Schulz’s TypeScript Evolution series, which you can find at https://mariusschulz.com/blog/series/typescript-evolution.

13.2 Reviewing code to catch problems machines can’t

Machines do only what they’re told. They don’t make mistakes; humans do. Whenever software misbehaves, it’s a human’s fault.

Code reviews exist so that humans can point out each other’s mistakes and improve a piece of software’s design. Additionally, code reviews help distribute ownership across a team and spread knowledge. If anyone needs to change a piece of code written by someone else, they’ve already read it before and feel more comfortable updating it.

Furthermore, code reviews can help catch mistakes that tests and types can’t. During reviews, others can flag, for example, that there’s an edge case for which there are no automated tests or that there are types that aren’t strict enough to prevent the program from getting to an invalid state.

If your team doesn’t have a formal code-review process yet, in the vast majority of cases, I’d recommend you to implement one. Even if you don’t use pull requests or any other formal methods, the mere process of having someone else proofread your code will yield significant benefits.

In this section, I’ll teach you a few techniques to ensure you and your team will get the most out of code reviews.

The first of these techniques is perhaps the most important to allow for others to perform thorough reviews: writing detailed pull request descriptions. When others understand your change’s intent and all the nuances involved, they can avoid adding redundant comments that point out something you’ve already taken into account when writing code.

Personally, I like to include the following information in the pull requests I open:

  • A quick summary containing the change’s intent and any related issue-tracker tickets

  • An in-depth explanation of the problem I’m addressing or the nuances of the feature I’m implementing

  • A description of the pieces of code I’ve either written or updated, emphasizing the nuances and questions I had during implementation

  • A brief guide on how I’ve validated my changes so that others can confirm the code is correct

TIP If you use GitHub, you can create pull request templates with a separate section for each of these items so that others can quickly and easily understand how they should write their pull request descriptions.

Once pull request descriptions have this much level of detail, then it’s the reviewer’s task to communicate with the author to ensure the code is as high quality as it can be.

My first advice for reviewers is always to think of a pull request as a conversation. When doing a review, instead of only requesting changes, I recommend others to compliment the author’s elegant design and ask questions.

Reviewing pull requests with the intent of interacting with the author forces reviewers to pay more attention. Furthermore, this attitude fosters more meaningful and positive interactions.

During these interactions, I also recommend reviewers to clearly indicate whether a change request would block a pull request from receiving their approval. This indication helps teams waste less time discussing trivial or subjective matters that maybe the reviewers themselves didn’t think were so important.

Additionally, reviewers should also explain why they think a particular piece of code needs changes. By describing the advantages of adopting the suggested approach, they make debates more fluid and give the author more information to consider when making a decision.

The final and perhaps the most crucial advice for reviewers is to perform reviews with their text editor or IDE open. When writing code, authors don’t merely go through files in alphabetical order and implement changes. Instead, they find a change’s entry point and navigate through the dependency graph, changing the pieces of code they need. Therefore, reviews should not be linear. Reviewers should look through files according to the dependency graph and the changes being implemented, not in alphabetical order.

Reviewing code with your editor or IDE open allows you to check other pieces of code that might not have been changed but do have an impact on whether a pull request’s changes are valid.

To summarize, here is a list with all of this chapter’s advice on how to review pull requests:

  1. Write detailed pull request descriptions.

  2. Consider every pull request a conversation—review with the intent of adding comments, regardless of whether you’re suggesting changes.

  3. Clearly indicate whether you consider a suggested change to be required for the author to obtain your approval.

  4. Explain why you think a particular piece of code needs changes.

  5. Review pull requests with your text editor or IDE open. Follow the code; do not review it linearly.

In the teams and open source projects I’ve participated on over the years, one of the main compliments I get is that my pull request descriptions and reviews are detailed and comprehensive. This discipline has yielded significant productivity increases and more positive interactions plenty of times.

Finally, to make your changes easier to read and digest, try to keep your pull requests small. If you’re working on a large feature, you can split it into multiple pull requests or request others to review intermediary states of your changes. Often, when pull requests are too big, people will miss important details among the numerous lines of code in the VCS diff.

13.3 Using linters and formatters to produce consistent code

A consistent theme throughout this book has been that if a machine can do a particular task, you should delegate that task to it. Linting and formatting are two such tasks.

Linting, similar to type checking, is a kind of static analysis process. When using a linter, it will analyze the code you’ve written and validate whether it matches a configurable set of rules. Linters can indicate issues that could lead to bugs or inconsistencies in how the code has been written.

You can use a linter to trigger warnings when, for example, you use repeated names for properties in an object, when you declare unused variables, when you write unreachable return statements, or when you create empty code blocks. Even though all of these constructs are syntactically valid, they can be unnecessary or lead to defects.

By using a linter, you leverage the machine’s capabilities of tirelessly checking code and free the other members of your team to pay more attention to the actual semantics of the code you’ve written. Because others can trust that the machine has done its job in catching trivial issues, others can focus on reviewing your code’s semantics instead of pointing out that you have duplicate if/else statements, for example.

/testing-javascript-applications/example.js                              
  6:14  error   This branch can never execute.                           
                Its condition is a duplicate or covered by previous
                conditions in the if-else-if chain
                no-dupe-else-if                                          

The file in which the linting error was found

The error’s line and column, followed by an explanation about what the problem is

The name of the linting rule your code violates

Furthermore, many tools and frameworks offer linter plugins so that your linter can warn you about bad practices. Suppose you’re writing a React application. In that case, you can use a plugin to configure your linter to emit warnings if you’ve forgotten to specify the PropTypes of a component’s property.

At the time of this writing, the most popular JavaScript linting tool is called ESLint. It’s an extensible and easy-to-use linter that you can install as a dev dependency by using npm install --save-dev eslint. Once you’ve installed it, you can create a configuration file by running ./node_modules/.bin/eslint --init and validate your code by running ./node_modules/.bin/eslint ..

TIP As you’ve seen earlier in this book, you can omit the path to the binaries within the node_modules folder if you create an NPM script in your package.json that runs eslint. In most projects, that’s what you’ll probably want to do.

In addition to pointing out dangerous constructs or bad practices, linters can also indicate and fix stylistic issues, such as the inconsistent usage of double quotes and single quotes, unnecessary parentheses, or extra empty lines.

Personally, I don’t like to use linters to catch stylistics issues. Instead, I prefer to use an opinionated code formatter, like Prettier, for example.

The problem with using a linter to deal with code style is that you can configure what your stylistic rules are, and, even though this statement may seem counterintuitive, having more choice is usually bad when it comes to formatting. Code formatting is highly subjective, and everyone has their preferences in regard to whether you should use double quotes or single tabs and tabs or spaces—despite spaces being much better, of course.

Honestly, code style doesn’t matter as long as it’s consistent. I don’t mind if others prefer to use tabs rather than spaces, as long as the whole codebase uses tabs.

By using Prettier, you can skip all the hours of pointless subjective discussions and defer to Prettier’s choices instead—as I’ve done when writing this book’s examples.

Additionally, Prettier can make code easier to read and more pleasant to work on.

NOTE I like to say that discussing code style preferences is always bike-shedding. Bike-shedding occurs when people waste way too much time discussing trivial and easy-to-grasp aspects of a project instead of focusing on the most complex and critical tasks necessary for it to be done.

This term was initially coined by Poul-Henning Kamp. It refers to Cyril Northcote Parkinson’s fictional example for his law of triviality, which states that groups of people typically give disproportionate weight to trivial issues. In his example, Cyril mentions that a committee whose job is to approve a nuclear power plant’s plan will often spend an immense amount of time discussing which materials to use for its bike shed instead of analyzing the actual power plant’s plan.

Using Prettier is incredibly simple. To start formatting your code with Prettier, you only need to install it as a dev dependency with npm install --save-dev prettier and then use ./node_modules/.bin/prettier --write ..

TIP In my own projects, I often integrate Prettier with Git hooks so that it will automatically format all the code I commit. For that, I use husky, a tool I covered in chapter 12.

13.4 Monitoring your systems to understand how they actually behave

I’ve never heard of a piece of software that doesn’t have any bugs. Up to today, much has been said and written about correctness, but the current state of the software industry clearly indicates we haven’t yet figured out how to write bug-free software.

As I explained in chapter 3, not even codebases with 100% of code coverage mean your software is free of bugs. Sometimes, users will prove your software with a particular input you didn’t expect, and bugs will happen.

Monitoring works on the assumption that problems will eventually happen, and that it’s better to notice them before your customers do. By monitoring your software, you can understand which of your assumptions about how the code works aren’t true.

Additionally, well-implemented monitoring systems will be able to give you insight on your software’s performance, resource consumption, and utilization.

Without collecting data on what your software currently does, it’s impossible to optimize its performance, because you’ll have no benchmark against which to compare your changes and because you don’t know where bottlenecks are.

Or, as Rob Pike states in the first of his five rules of programming (https://users.ece.utexas.edu/~adnan/pike.html):

You can’t tell where a program is going to spend its time. Bottlenecks occur in surprising places, so don’t try to second guess and put in a speed hack until you’ve proven that’s where the bottleneck is.

—Rob Pike

Imagine, for example, that your customers are complaining about how long it takes for your website to load. How will you make significant improvements to your pages’ load times if you don’t know how these pages currently behave? You can certainly try to guess where the bottlenecks are, but, without measuring, you’re shooting in the dark.

On the other hand, if you have adequate monitoring, you can try a few versions of your website, each of which has different changes, and monitor how they perform, so that you can actually understand each change’s impact.

Furthermore, measuring allows you to avoid prematurely optimizing your software. Even though you may have written a suboptimal algorithm, perhaps it’s already good enough for the load your application experiences.

Measure. Don’t tune for speed until you’ve measured, and even then don’t unless one part of the code overwhelms the rest.

—Rob Pike

Finally, one last important aspect of setting up a monitoring infrastructure is having the capability of sending out alerts in case your monitoring systems detect anomalies. If your API is unreachable or if something that affects business value is not working, someone should wake up.

For that to happen, make sure you’re tracking all the parts of your code that affect the value your customers get from your software. In addition to enabling alerting, measuring the aspects of your software that are more intimately tied with the value it provides to customers is what will allow you to make effective business decisions in the future.

Because this is a book about tests, I won’t go into detail about how to set up monitoring systems or what adequate monitoring infrastructures look like. Doing that would require an entire book—actually, it would probably require many.

I thought it was necessary, however, to emphasize the role that monitoring plays in writing high-quality software—investing time in learning more about how do it properly will pay off when building software at scale.

13.5 Explaining your software with good documentation

After more than half a thousand pages, saying I’m a big fan of writing is somewhat redundant. Nevertheless, it’s important to emphasize the positive effect that well-written pieces of documentation can have in a codebase.

Its first benefit is well-known: it helps others understand the codebase more quickly. Documentation is especially helpful for others to understand not the code itself but, instead, why it’s been written in a particular manner. Personally, to keep documentation lean, I avoid describing how different pieces of code work and, instead, focus on explaining their intent.

The biggest problem with documentation is keeping it up-to-date. As you update your code, if your documentation goes out of sync, it will be more confusing for others to understand what the code is supposed to do because now they have two conflicting sources of information.

To avoid this situation, I personally like to keep my documentation as close to the code as possible. To achieve this goal, I prefer to use JSDoc to document my code using comment blocks instead of writing documentation separately using markdown files.

Documenting your software within code files makes it almost impossible for others to forget they need to update documentation when writing code. If the function someone is changing has a JSDoc block above it, others won’t need to spend time searching for a markdown file or updating a separate wiki.

Additionally, if you use JSDoc, you can easily generate static websites with your software’s documentation and publish them to the internet. Others won’t necessarily have to look through your code to read its documentation.

Furthermore, many text editors and IDEs can parse JSDoc and display tooltips with functions’ pieces of documentation as you write code.

NOTE If you’d like to start using JSDoc, I’d highly recommend you to read the tool’s official documentation at https://jsdoc.app.

The second and, in my opinion, most impactful benefit of documentation is still not as widespread: writing documentation forces authors to reflect on their choices and structure their thoughts precisely. This kind of work, in turn, tends to lead to friendlier designs and helps authors themselves develop a better understanding of the codebase. As Pulitzer-Prize–winning and National Book Award author David McCullough once put it, “Writing is thinking. To write well is to think clearly. That’s why it’s so hard.”

Personally, I often like to write documentation before I write any code. By explaining the code’s intent before I write it, I usually worry less about implementation details and focus on what the module’s consumers will need.

Finally, my last advice for engineers is also to document their processes and contribution policies. Having an up-to-date and well-written work agreement helps others understand what’s expected from them and by when.

Documenting, for example, that you expect every pull request to include automated tests, helps to formalize it as a good practice and set expectations within the team.

Summary

  • When you write tests, you’re running experiments. You execute your programs with sample inputs and observe how your program behaves. Then, you choose to extrapolate those conclusions and trust that the program will behave similarly in the future for all inputs. Types, on the other hand, allow you to prove that your program can work only in a particular way.

  • By using a type system, you can prove properties of your programs without having to execute the program itself. This is the reason why type checking is considered a process of “static analysis.”

  • The strict usage of type systems helps you make invalid states impossible to represent, which, therefore, makes it impossible for you to make mistakes that lead your software to those invalid states.

  • Additionally, type systems reduce the possible universe of inputs certain functions can take, making software easier to validate because you have fewer cases for which you need to write automated tests.

  • Code reviews exist to catch mistakes that machines can’t. Even though you can use automated tests to validate your code, you must ensure that your automated tests are correct and that they fulfill the expected business goals. To validate those two aspects of software development, you need an extra pair of eyes to point out mistakes.

  • When submitting pull requests, write thorough descriptions. These descriptions facilitate your reviewer’s job because they help others understand what you’re trying to accomplish and why you’ve made certain decisions.

  • If you’re a reviewer, treat pull requests as conversations. By reviewing pull requests with the intent of communicating with the author, you will be able to make sure you’ve asked the relevant questions, and, because you’re trying to create a meaningful communication bridge, you will inevitably pay more attention. Additionally, writing compliments creates a healthy bond between individuals in the team.

  • Clearly indicate in your reviews which changes are going to prevent the pull request from getting your approval. This attitude helps teams avoid discussions about trivial suggestions that both sides do not consider to be relevant.

  • Do not review code linearly. Instead of skimming through multiple files, try to follow the author’s train of thought. Implementing changes is not a linear process, and, therefore, linear reviews do not allow reviewers to jump through the code’s dependency graph properly.

  • Linting is a kind of static analysis process, similarly to type checking. Linters analyze the code you’ve written and validate whether it matches a configurable set of rules, thus indicating issues that could lead to bugs or inconsistencies.

  • Formatters focus exclusively on stylistic issues. They ensure that you’re following consistent code style and make code easier to read.

  • Linters and formatters reduce the number of nitpicky comments in pull requests because code standards are automatically enforced and validated by machines rather than humans.

  • Monitoring allows you to understand how your software behaves when it’s in the hand of customers. Therefore, it helps you detect which are the false assumptions you’ve made in regard to how your program works.

  • By monitoring your software, you can understand where its bottlenecks are and measure improvements, thus avoiding premature optimization and the overhead of a trial-and-error approach to software updates.

  • Setting up alerting on top of your monitoring infrastructure helps ensure that your team will act promptly when the application’s business value is affected.

  • When writing documentation, focus on explaining the intent of the code instead of its inner workings so that you can keep your documentation lean.

  • You can bundle your documentation into your codebase using tools like JSDoc. These tools cause the code to become the single source of truth and diminish the time and effort necessary to update documentation.

    Writing documentation before you write code can help you elucidate what is it that you’re trying to achieve, because when doing so, you will focus on a module’s interface and intent instead of worrying too much about its implementation details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.253.161