How to Write Good Code: 10 Beginner-friendly Techniques for Instant Results

Share this article

How to Write Good Code: 10 Beginner-friendly Techniques for Instant Results

As a beginner developer, improving your code skills is probably one of your top priorities. But where do you start? With so much information out there, it can be tough to know which techniques are worth learning and which ones will actually help you write better code. 

In this blog post, we’ll share 10 beginner-friendly techniques that will help you write better code – right away. So if you’re ready to take your coding skills to the next level, keep reading!

Key Takeaways

  1. Plan Ahead: Before diving into coding, spend time planning your approach. Understanding the problem, its inputs and outputs, and potential edge cases can save significant time and enhance code quality.
  2. Use Clear Naming and Modularity: Adopt clear, descriptive names for variables and functions to make your code more readable and self-explanatory. Break your code into small, modular functions, each responsible for a single piece of functionality, to facilitate easier testing, debugging, and future reuse.
  3. Embrace Best Practices: Follow established coding best practices such as adhering to the DRY principle to avoid repetition, utilizing appropriate data structures, commenting your code to explain complex logic, and using version control systems like Git to track changes and collaborate effectively. These practices not only improve code quality but also make maintenance and teamwork more manageable.

1. Start with a plan

Making a plan

One of the best ways to write better code is to start with a plan. Before you start coding, take a few minutes to think about what you want your code to do.

Don’t just jump into writing code because you think you know what needs to be done. Take some time to really understand the problem at hand.

  • What are the inputs and outputs?
  • What are the expected results?
  • What are the steps involved in getting from one to the other?
  • What data structures will you need?
  • Are there any edge cases that need to be considered?

Answering these questions before you start coding can help you avoid getting lost in rabbit holes for hours or days. It gives you a chance to solidify your mental conceptualization of how the project will work, validate it against any clear leaps of magical thinking, and develop a set of test cases to check your work against as you go.

Winging it can be fun (and often tempting) but this approach doesn’t need to box you in constrictive ways or take up hours of time. Even a few minutes sketching a diagram on paper before you fire up your editor will pay outsized dividends.

Developing a clear understanding of what needs to be done enables you to turn your idea into a concrete plan. Even if the high-level program structure you develop isn’t perfect (and let your inner perfectionist off the hook — it won’t be!), you’ll find the resulting code easier to read and the complexity of extending the code much more manageable.

And while your code will be cleaner, you’ll really benefit the most from countless hours saved on problems that could’ve been avoided if you’d identified a few unknowns and laid out the plan. That’s countless hours gained to tackle more advanced problems and develop skills that benefit you and your career.

Tips for developing a high-level plan

Tips for developing a high-level plan
  1. Develop a clear understanding of the problem you’re trying to solve.
  2. Before you start coding, take some time to think about what you want your code to do in order to solve that problem.
  3. Write pseudocode before you start coding. Pseudocode is code that isn’t quite real code yet. It’s useful for sketching out the structure of your code without getting bogged down in the details.
  4. Draw a diagram. Visualizing the problem can help you better understand what needs to be done and how the different pieces fit together.
  5. Check your work. Once you have a plan, check it against any clear leaps of magical thinking and make sure it’s feasible.
  6. Use inline comments to explain your thought process. Once you’re writing your code, include inline comments to explain what you’re doing and why. These comments can be very helpful when you or someone else comes back to the code later. This is especially true if you’re working on a complex problem that’s likely to be confusing to someone else.

2. Write meaningful variable and function names

One of the hallmarks of well-written code is that it’s easy to read and understand. A big part of making your code easy to read is using meaningful variable and function names.

Picking good names for things is hard. But it’s important, even in web development. Your variable and function names are usually the first thing people look to when they’re trying to understand your code.

Consider the following example:

let x, y, z;

function f() { 

// function

}

This code is not very easy to read. What do x, y, and z represent? What does f() do?

Now consider this example:

let firstName, lastName;

function printFullName(firstName, secondName) { 

// function

}

This code is much easier to read. The variable names are informative, and the function name gives us a good idea of what it does.

You’ll also be more likely to catch errors when you’re reviewing your code. It’s much easier to spot a mistake — like the wrong variable being passed to a function — when names are descriptive. Otherwise, you have to hold the meaning of that variable in your working memory at all times.

It’s easy enough to remember why we defined the variable a, but holding ciphers in your working memory becomes overwhelming fast — well before you define z. This becomes a cognitive bottleneck that can seriously limit the scope of complexity you’re able to manage.

You should also adopt a consistent style for formatting the names. When people refer to naming conventions in development, they’re usually talking about the way capitalization and dividers are used to enhance readability.

Here are the naming conventions you’ll see most often in development:

  • Camel case: Variable names are written in a series of words that are joined together, with each word except the first beginning with a capital letter. Example: firstName, lastName, printFullName(). Camel case is very common in JavaScript.
  • Pascal case: Pascal case is similar to camel case, but the first word is also capitalized. Example: FirstName, LastName, PrintFullName().
  • Snake case: Snake case uses all lowercase letters and underscores to separate words. Example: first_name, last_name, print_full_name().
  • Kebab case: Kebab case is similar to snake case but with hyphens instead of underscores. Example: first-name, last-name, print-full-name().

Once you pick a naming convention, it’s important to be consistent and stick to it.

For example, you might decide to use camel case (firstName) for variable names and snake case (print_full_name()) for functions. In this case, using different conventions makes consistency especially important for readability. You’ve implied that each naming convention has a meaning.

If that changes at random, others who need to interpret your code will have to slow down and may misunderstand it or simply have to slow down and apply more focus than necessary.

Tips for writing clear variable names

Naming conventions

Variable and function names should be:

  • Descriptive.
  • Easy to remember and pronounce.
  • Consistent with other names in the code.

To achieve this, you should:

  • Use descriptive names. The name of a variable or function should describe its purpose.
  • Avoid single letter names, unless the meaning is very clear from the context. For example, it’s usually okay to use i as an index in a for loop, because that’s a common convention.
  • Avoid magic numbers. A magic number is a numeric literal that’s used in the code without a clear explanation of its meaning.
  • Decide on a naming convention, then stick to it.
  • As always, comment your code. When a clear name isn’t enough and you do need to review the original function or variable, you’ll be able to refresh your memory quickly.

When you’re picking names for things, ask yourself the following questions:

  • What is this variable or function for?
  • Does its name describe its purpose?
  • Is it easy to remember when I’m and pronounce?
  • Is it consistent with the other names in the code?

If you can’t answer all of these questions easily, it’s probably a good idea to choose a different name.

3. Write small, modular functions

Use small, modular functions

Functions are one of the most powerful tools in a programmer’s toolbox. They allow you to take a large problem and break it down into smaller, more manageable pieces.

Smaller functions are easier to test, easier to debug, and easier to reuse. They also make your code more readable because the purpose of each function is clear.

Consider this example:

function multiplySquaredNumbers(x, y) {
  let xSquared = x * x;
  let ySquared = y * y;
  return xSquared * ySquared;
}

console.log(multiplySquaredNumbers(5, 6)); // Output: 360

As you can see, this function takes two arguments. It declares variables to manage the results of squaring the input parameters so that they can be operated on by subsequent lines. Here, this happens on the return line, when those variables are multiplied before a single number is passed back to the caller.

There are other ways we could approach simplifying this function, which you may already have spotted. Here’s one:

function multiplySquaredNumbers(num1, num2) {
    return Math.pow(num1, 2) * Math.pow(num2, 2);
}

console.log(multiplySquaredNumbers(2, 3));

But to demonstrate the utility of modular code, we’ll outsource the process of squaring numbers to its own function.

function square(x) {
  return x * x;
}

function multiplySquaredNumbers(x, y) {
  return square(x) * square(y);
}

console.log(multiplySquaredNumbers(5, 6)); // Output: 360

At first glance, it can be hard to see how this approach helps us write better code. This example is too simple (and relies on too many basic operators) to reduce the number of lines of code. In fact, we added a couple.

Concise code is always preferred to unnecessarily verbose code. But unless you’re completing a code challenge, don’t achieve it at the cost of robust, readable code.

It’s important to understand that code modularity isn’t about an ascetic pursuit of minimalism in your code. It’s about benefiting from the time you’ve spent solving problems when they come up again.

Now, any time we want to square a number in the future, we’ll be able to use our modular function to do the job, even if we never need to multiply two squared numbers again. We’ve already told the computer how to do this job. We may as well benefit from it!

If we adopt the approach in the original example, we would have to tell the interpreter how to proceed any time we wanted to square some numbers for a subsequent operation.

This is a simple example, but it illustrates how functions can be used to break down a larger problem into smaller pieces.

The problems you need to solve repeatedly are usually more complex in web development. For example, you might need to display a list of data from an API call. This involves fetching the data, iterating over it, and dynamically creating elements that display some of that data on the page.

Solving this problem once is great, but if you need to do it every time an API call is made or a list of data is updated, you’d have to duplicate a lot of code. This would quickly become unmanageable, especially as the number of different places where this list might need to be displayed grows.

Instead, you can create a function that takes some data and returns the elements you need to display that data on the page. Then, whenever you need to create those elements, you can just call the function with the appropriate data. This would allow us to keep our code DRY and avoid repeating ourselves.

Tips for writing modular functions

There are some best practices you can follow when writing modular functions:

Keep functions small by giving them a single responsibility

When you’re writing a function, think about what it’s supposed to do and only have it do that.

It can be tempting to write a large, all-encompassing function that does everything in one go. But this makes your code more difficult to reason about and can lead to errors.

It’s usually better to write several small functions that each do one thing. These are easier to test and more likely to be reused in different parts of your code.

Name your functions descriptively

Function names should be clear and descriptive so that you (and other developers) can easily understand what they do when reading the code. We’ve discussed naming, but this is especially important for functions that are used repeatedly throughout the codebase.

Avoid side effects

A function is said to have a side effect if it modifies something outside of its scope. For example, a function that takes an array as an argument and sorts the array would be considered to have a side effect.

Functions without side effects are called pure functions. These are generally preferred because they’re more predictable.

It can be difficult to avoid side effects all the time, but it’s something to keep in mind when writing functions.

Use arguments wisely

When you’re deciding what arguments to include in a function, think about whether they’re really necessary.

Arguments are often used to make a function more flexible so that it can be used in different situations. But too many arguments can make a function difficult to understand and use.

It’s usually better to include a smaller number of well-chosen arguments than a larger number of less important ones.

4. Use data structures appropriately

Data structures are ways of organizing data so that it can be used efficiently. There are many different types of data structures, but some of the most common are arrays and objects.

Arrays are lists of data. They can be used to store any type of data, but each item in an array must have the same type. Arrays are declared using square brackets:


const arr = ['a', 'b', 'c'];

Objects are collections of data that are organized using key-value pairs. The keys are used to access the values, which can be any type of data. Objects are declared using curly braces:


const obj = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3',
};

Data structures should be used appropriately to make your code more readable and efficient. For example, if you have a list of data that needs to be displayed on a page, using an array would be more appropriate than using an object. This is because it would be easier to iterate over the array and create the elements needed to display the data.

On the other hand, if you have a collection of data that needs to be displayed on a page, but each piece of data also has some associated metadata, using an object would be more appropriate than using an array. This is because it would be easier to access the data and metadata using the keys.

5. Comment your code liberally

Comment your code liberally

Comments are lines of code that are not executed but are there for the developer to leave notes for themselves or others. In JavaScript, comments are denoted with // for single-line comments and /* */ for multi-line comments:

// this is a single-line comment

/*

  this is
  a multi-line
  comment

*/

Comments are a great way to improve the readability of your code. Use comments to explain what your code is doing and why you’re doing it.

Comments are important for two main reasons: they can help you remember what your code is doing, and they help others understand your code. It’s important to get in the habit of commenting your code as you write it. This will help you keep track of your thoughts and make it easier for others to understand your code.

One common convention is to use TODO comments to leave notes for yourself about things that need to be done:

// TODO: Implement login functionality

Another common convention is to use FIXME comments to leave notes for yourself about things that need to be fixed:

// FIXME: This code is not working properly

This is a helpful way to keep track of what needs to be done, and it also makes it easy for others to see what needs to be done.

In general, it’s a good idea to leave comments whenever you’re not sure what something is supposed to do, or if you think there might be a better way to do something. Comments are also generally used to explain complex or non-obvious code.

It’s important to remember that comments should be used to improve the readability of your code, not to make it more difficult to understand. If you find yourself writing a comment that is longer than the code it is commenting on, that is a sign that your code is not readable and should be refactored.

Tips for commenting code

  • Use comments to explain what your code is doing and why you’re doing it.
  • Use comments to leave notes for yourself about things that need to be done or fixed.
  • Use comments to explain complex or non-obvious code.
  • Use comments to enhance readable code, not as a crutch.
  • Comment your code as you write it. Don’t wait until later.
  • Don’t over-comment your code. Only comment the parts that need explanation.
  • Use clear and concise language in your comments.
  • Avoid using acronyms or jargon.
  • Keep your comments up-to-date with your code. If you change your code, change your comments.
  • Delete obsolete comments.

4. Indent your code for readability

Indenting your code makes it easier to read, and it also helps you spot errors. When your code is properly indented, it’s easier to see the structure of your code and where each section begins and ends. This can be a helpful way to debug your code and find errors.

In JavaScript, the standard indentation is two spaces. In Python, the standard indentation is four spaces. In a language like Python where indentation is significant, using the wrong indentation can cause your code to break.

But even in a language like JavaScript, where indentation is purely a matter of presentation, it’s important to be consistent with your indentation. Inconsistent indentation can make your code more difficult to read and understand.

The main reason for indenting code is to improve readability. But indenting code can also help you find errors. If your code is properly indented, it’s easier to see when something is out of place.

For example, take a look at the following code examples:

// Unindented code

function printHello() {
console.log("Hello, world!");
}
printHello();

// Indented code

function printHello() {
  console.log("Hello, world!");
}

printHello();

In the unindented code, it’s difficult to see that the console.log() statement is inside the printHello() function. But in the indented code, it’s clear that the console.log() statement is inside the printHello() function. This makes it easier to spot errors, like if you forget to add a curly brace.

Indenting your code is a matter of style, but it’s important to be consistent with your indentation. Most programming languages have conventions for how code should be indented, so it’s a good idea to follow those conventions.

In general, you should indent your code whenever you start a new block. A block is a section of code that is executed together. For example, a block can be a function, an if statement, or a for loop.

6. Use whitespace to improve readability

In addition to indenting your code, you can also use whitespace to improve its readability. By adding extra spacing between lines of code, you’ll make your code easier to scan and understand. This is especially helpful when you’re reviewing large blocks of code.

The space itself makes it easier to keep track of your reading position, just as paragraphs do for natural languages. But whitespace is best at making code easier to read when it groups related lines of code together.

It’s a common practice to put a blank line between the end of a function and the beginning of the next function.

function printHello() {
       console.log("Hello, world!");
}

function printWelcome(name) {
      console.log("Hello, " + name);
}

You might add a blank line between the clauses of a conditional statement.

function printWelcome(name) {

if (name === "John") {
     print("Welcome, John!");
} 

else {
     print("Welcome, stranger!");
}

You might also add a blank line between the lines of code that declare variables and the lines of code that use those variables.

<?php
$name = "John";
$location = "Sydney";

echo "Welcome to $location, $name!";
?>

Whitespace and indentation both provide their own benefits, but they work together to create a visual hierarchy that clarifies the flow of execution. When you combine the use of whitespace to group related lines and indentation to indicate scope, your code and its readability will benefit the most.

7. Use arrays and loops for efficiency

Arrays and loops are foundational but powerful tools that can help you write better code. If you’ve started learning to code, you probably already know about them.

By using arrays, you can store data in an organized way. This can make your code more efficient and easier to read. Loops, on the other hand, can help you automate repetitive tasks.

Once you know how to use them properly, they can save you a lot of time and effort. For example, they can often eliminate the need for complicated, nested conditional blocks.

Nested if statements are hard to read because they have so many lines of code and involve so many forks in the logic flow.

Here’s an example of a nested if statement:

if (x > 0) {
  if (x < 10) {
    print("x is between 0 and 10");
  } else {
    print("x is greater than 10");
  }
} else {
  print("x is less than or equal to 0");
}

The same logic can be written more clearly using an array and a loop:

let numbers = [-5, 0, 5, 10, 15];

for (let i = 0; i < numbers.length; i++) {

  let x = numbers;

  if (x > 0 && < 10) {
    console.log(`x is between 0 and 10`);

  } else if  (x > 10) {
    console.log(`x is greater than 10`);

  } else {
    console.log(`x is less than or equal to 0`);
  }
}

This code is easier to read because it’s more concise and the logic flow is more linear. The for loop iterates over the elements of the array, and the if statement tests each element to see if it meets the specified condition.

This is often more efficient because it eliminates the need for multiple conditional tests.

8. Write self-documenting code whenever possible

Self-documenting code is code that is easy to read and understand without the need for comments. This type of code is written in a way that makes its purpose clear.

This doesn’t replace good commenting habits, but it does force you to keep your high-level program structure in mind. You’ll produce more understandable code that’s easier to maintain and less error-prone.

There are many ways to make your code self-documenting. We’ve already covered some of them:

  • Use clear and descriptive variable and function names.
  • Write short functions that do one thing and do it well.
  • Avoid magic numbers (numbers with no obvious meaning) by using named constants.
  • Use whitespace to separate code into logical chunks.
  • Use clear and consistent coding conventions. This makes your code easier to read and understand, even for people who are not familiar with your codebase.

Here are some other ways to make code self-documenting:

  • Avoid unnecessary code. This includes dead code (code that is no longer used but has not been removed) and comments that state the obvious.
  • Write code that is easy to test. This means that your code should be modular and have a well-defined interface. It should also handle errors gracefully and in a consistent way.
  • Keep the codebase small. This makes it easier to find what you’re looking for and understand how the code works.
  • Keep your code well-organized. This means using a consistent coding style and structure, and using comments to explain complex code.
  • Documentation is important, but self-documenting code is even better. It’s easier to read, understand, and maintain. So next time you’re writing code, ask yourself if there’s anything you can do to make it more self-documenting.

These are just a few guidelines. As you become a more experienced developer, you’ll find many more ways to make your code self-documenting.

9. Don’t Repeat Yourself (DRY)

One of the most important principles of good coding is the DRY principle: don’t repeat yourself.

This means that you should avoid duplicating code whenever possible. Duplicated code is more difficult to maintain and more error-prone.

There are many tools that can be employed to avoid duplication in your code.

  • Functions and modules. Functions allow you to encapsulate code that you want to reuse, which we looked at earlier (when we first mentioned the DRY principle). Modules allow you to group related functions together.
  • Data structures. Data structures can be used to store information in a way that is easy to access and modify. For example, if you have a list of names, you can store them in an array rather than hard-coding them into function calls throughout your code.
  • Inheritance. A more advanced way to avoid duplication is to use inheritance. This is a way to share code between classes by having one class inherit from another. We won’t go into detail about inheritance here, but suffice it to say that it’s a powerful tool that can help you avoid duplicating code.
  • Libraries. Finally, you can avoid duplication by using tools and libraries. There are many open-source libraries that you can use to perform common tasks. For example, the lodash library provides a wide range of utility functions.

The DRY principle is one of the most important principles of good coding. It’s important to avoid duplicating code whenever possible. This saves you time because you only solve problems once, and only need to change one implementation of the solution when other factors change.

And because you will only need to fix one implementation when things break, DRY code is easier to maintain and less error-prone.

Tips for writing DRY code

  1. Avoid repeating yourself by trying to reuse code where possible. If you know you’ll be doing something again elsewhere in your code, you can write that code as a discrete entity the first time and avoid a trip back to refactor.
  2. When you reuse code, modularize it. Don’t copy the solution to the new location. Instead, move it into the appropriate type of object or data structure, then reference it.
  3. Refactor your code when you see a significant opportunity to benefit from rewriting it as DRY code. That means restructuring your code without changing its functionality. Refactoring can sometimes be a procrastination trap, but if you realize that you’ll need parts of a large function again, it’s worth doing.
  4. Use libraries and frameworks to avoid reinventing the wheel. If you shouldn’t repeat yourself, why should you write code to solve a problem that’s already been solved?
  5. Use inheritance to share code between classes.
  6. Follow the DRY principle when creating documentation – don’t duplicate information unnecessarily.
  7. Use clear variable and function names, and comment your code where necessary.

11. Write SOLID Code

One popular framework for thinking about how we write software is called SOLID.

SOLID is an acronym that references five key software design principles and was coined by Robert C. Martin, a founder of the Manifesto for Agile Software Development and author of Clean Code.

The five design principles of SOLID are:

  • Single responsibility principle. This principle states that every class or module should have one (and only one) reason to change. In other words, each class or module should be responsible for one thing only.
  • Open/closed principle. This principle states that software should be open for extension, but closed for modification. That is, you should be able to extend the functionality of a class or module without having to modify the code itself.
  • Liskov substitution principle. This principle states that subclasses should be substitutable for their superclasses. That is, a subclass should be able to stand in for its superclass without causing any problems.
  • Interface segregation principle. This principle states that clients should not be forced to depend on methods they don’t use. In other words, each interface should be small and focused on a specific purpose.
  • Dependency inversion principle. This principle states that dependencies should be inverted. That is, high-level modules should not depend on low-level modules. Instead, both should depend on abstractions.

You don’t need to memorize each of these principles, but they’re worth being aware of. As you start to write better code, you’ll likely find yourself naturally following these principles.

12. Don’t Reinvent the Wheel

One of the most important principles of good coding is don’t reinvent the wheel. This means that you should use existing libraries, tools, and frameworks whenever possible, rather than writing your own code from scratch.

There are many reasons to follow this principle. First, it saves you time. You don’t have to write code that’s already been written. Second, it reduces the amount of code you have to maintain. And third, it increases the chances that someone else will find and fix any bugs in the existing code.

Of course, there are exceptions to this rule. And if you need something that doesn’t exist yet, you’ll have to create it yourself. But in general, it’s best to reuse existing code whenever possible.

13. Use a Version Control System

A version control system is a tool that allows you to track changes to your code over time.

This can be a helpful way to revert back to previous versions of your code, or to see who made changes to your code and when. Using a version control system can also help improve collaboration, as it allows multiple people to work on the same codebase simultaneously.

There are a few different version control systems available, but some of the most popular ones include Git and Mercurial.

We recommend learning Git, because you can safely assume that most teams you join in the future will be using it.

GitHub is a popular online service that provides a web-based interface for Git repositories. It is built on top of Git and is one of the most popular services teams use to collaborate on code today. Even as a beginner, you’ve likely come across it.

If you’re interested in learning more about version control, we recommend checking out some of the resources below:

Conclusion

Writing good code is an important skill for any developer, but it’s one that takes time and practice to master.

If you’re just getting started, the techniques in this post will help you write better code right away.

And as you continue to improve your skills, keep these tips in mind and refer back to them when necessary. With practice, you’ll be writing great code in no time!

FAQs on Writing Good Code

What is the importance of writing good code?

Writing good code is crucial for creating software that is maintainable, scalable, and efficient. It ensures that the codebase is easy to understand, modify, and debug, leading to better collaboration among developers and a more robust product.

What are the key principles of writing good code?

Some key principles include clarity, simplicity, modularity, and efficiency. Code should be easy to read and understand, with well-named variables and functions. It should also be modular, allowing for easy updates and maintenance, and efficient to ensure optimal performance.

How can I improve code readability?

Use meaningful variable and function names, follow a consistent coding style, and add comments where necessary. Break down complex tasks into smaller, well-named functions, and organize code logically. Adopting a coding standard or style guide can also enhance readability.

What is the significance of code comments?

Comments serve as documentation for your code, explaining its purpose, logic, and any potential pitfalls. However, it’s important to balance comments with writing self-explanatory code. Comments should be used sparingly and kept up-to-date to avoid confusion.

How can I make my code more maintainable?

Break down your code into smaller functions or modules, adhere to a consistent coding style, and avoid duplicating code. Writing unit tests and keeping them up-to-date can also contribute to maintainability, as they catch regressions when changes are made.

Is there a recommended coding style to follow?

The choice of coding style often depends on the programming language and the development team’s preferences. However, adopting a well-established style guide for the specific language, such as PEP 8 for Python or the Google Style Guide for various languages, can be beneficial.

Are there tools available to assist in writing good code?

Yes, there are various tools and linters that can analyze your code for style violations, potential bugs, and other issues. Examples include ESLint for JavaScript, Pylint for Python, and SonarQube for multiple languages. Integrating these tools into your development workflow can help maintain code quality.

Joel FalconerJoel Falconer
View Author

Joel Falconer is a technical content strategist. He has been managing editor at SitePoint, AppStorm, DesignCrowd, and Envato, and features editor at The Next Web.

Beginnercode formattingDRYfunctional programminglearn to codeSOLIDversion control
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week