Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - practice-proactive/code-review-tips: :microscope: Common problems to look for in a code review · GitHub
Skip to content

Latest commit

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

code-review-tips

Table of Contents

  1. Introduction
  2. Why Review Code?
  3. Basics
  4. Readability
  5. Side Effects
  6. Limits
  7. Security
  8. Performance
  9. Testing
  10. Miscellaneous

Introduction

Code reviews can inspire dread in both reviewer and reviewee. Having your code analyzed can feel as invasive as being screened by the TSA as you go off to your vacation. Even worse, reviewing other people's code can feel like a painful and ambiguous exercise, searching for problems and not even knowing where to begin.

This project aims to provide some solid tips for how to review the code that you and your team write. All examples are written in JavaScript, but the advice should be applicable to any project of any language. This is by no means an exhaustive list, but hopefully this will help you catch as many bugs as possible long before users ever see your feature.

Why Review Code?

Code reviews are a necessary part of the software engineering process because you alone can't catch every problem in a piece of code you write. That's ok though! Even the best basketball players in the world miss shots.

Having others review our work ensures that we deliver the best product to users and with the least amount of errors. Make sure your team implements a code review process for new code that is introduced into your codebase. Find a process that works for you and your team. There's no one size fits all. The important point is to do code reviews as regularly as possible.

Basics

Code reviews should be as automated as possible

Avoid discussing details that can be handled by a static analysis tool. Don't argue about nuances such as code formatting and whether to use let or var. Having a formatter and linter can save your team a lot of time from reviews that your computer can do for you.

Code reviews should avoid API discussion

These discussions should happen before the code is even written. Don't try to argue about the floor plan once you've poured the concrete foundation.

Code reviews should be kind

It's scary to have your code reviewed and it can bring about feelings of insecurity in even the most experienced developer. Be positive in your language and keep your teammates comfortable and secure in their work!

Readability

Typos should be corrected

Avoid nitpicking as much as you can and save it for your linter, compiler, and formatter. When you can't, such as in the case of typos, leave a kind comment suggesting a fix. It's the little things that make a big difference sometimes!

Variable and function names should be clear

Naming is one of the hardest problems in computer science. We've all given names to variables, functions, and files that are confusing. Help your teammate out by suggesting a clearer name, if the one you're reading doesn't make sense.

// This function could be better named as namesToUpperCasefunctionu(names){// ...}

Functions should be short

Functions should do one thing! Long functions usually mean that they are doing too much. Tell your teammate to split out the function into multiple different ones.

// This is both emailing clients and deciding which are active. Should be// 2 different functions.functionemailClients(clients){clients.forEach((client)=>{constclientRecord=database.lookup(client);if(clientRecord.isActive()){email(client);}});}

Files should be short

Just like functions, a file should be about one thing. A file represents a module and a module should do one thing for your codebase.

For example, if your module is called fake-name-generator it should just be responsible for creating fake names like "Keyser Söze". If the fake-name-generator also includes a bunch of utility functions for querying a database of names, that should be in a separate module.

There's no rule for how long a file should be, but if it's long like below and includes functions that don't relate to one another, then it should probably be split apart.

1: import_from'lodash';2: functiongenerateFakeNames(){3: // ..4: }...1128: functionqueryRemoteDatabase(){1129: // ... 1130: }

Exported functions should be documented

If your function is intended to be used by other libraries, it helps to add documentation so users of it know what it does.

// This needs documentation. What is this function for? How is it used?exportfunctionnetworkMonitor(graph,duration,failureCallback){// ...}

Complex code should be commented

If you have named things well and the logic is still confusing, then it's time for a comment.

functionleftPad(str,len,ch){str=str+'';len=len-str.length;while(true){// This needs a comment, why a bitwise and here?if(len&1)pad+=ch;// This needs a comment, why a bit shift here?len>>=1;if(len)ch+=ch;elsebreak;}returnpad+str;}

Side Effects

Functions should be as pure as possible

// Global variable is referenced by the following function.// If we had another function that used this name, now it'd be an array and it// could break it. Instead it's better to pass in a name parameterletname='Ryan McDermott';functionsplitIntoFirstAndLastName(){name=name.split(' ');}splitIntoFirstAndLastName();

I/O functions should have failure cases handled

Any function that does I/O should handle when something goes wrong

functiongetIngredientsFromFile(){constonFulfilled=(buffer)=>{letlines=buffer.split('\n');returnlines.forEach(line=><Ingredientingredient={line}/>)};// What about when this rejected because of an error? What do we return?returnreadFile('./ingredients.txt').then(onFulfilled);}

Limits

Null cases should be handled

If you have a list component for example, all is well and good if you display a nice beautiful table that shows all its data. Your users love it and you get a promotion! But what happens when no data comes back? What do you show in the null case? Your code should be resilient to every case that can occur. If there's something bad that can happen in your code, eventually it will happen.

classInventoryList{constructor(data){this.data=data;}render(){return(<table><tbody><tr><th>
ID
</th><th>
Product
</th></tr>
// We should show something for the null case here if there's
// nothing in the data inventory
{Object.keys(this.data.inventory).map(itemId=>(<trkey={i}><td>{itemId}</td><td>{this.state.inventory[itemId].product}</td></tr>))}</tbody></table>);}}

Large cases should be handled

In the list above, what would happen if 10,000 items came back from the inventory? In that case you need some form of pagination or infinite scroll. Be sure to always assess the potential edge cases in terms of volume, especially when it comes to UI programming.

Singular cases should be handled

classMoneyDislay{constructor(amount){this.amount=amount;}render(){// What happens if the user has 1 dollar? You can't say plural "dollars"return(<divclassName="fancy-class">
You have {this.amount} dollars in your account
</div>);}}

User input should be limited

Users can potentially input an unlimited amount of data to send to you. It's important to set limits if a function takes any kind of user data in.

router.route('/message').post((req,res)=>{constmessage=req.body.content;// What happens if the message is many megabytes of data? Do we want to store// that in the database? We should set limits on the size.db.save(message);});

Functions should handle unexpected user input

Users will always surprise you with the data they give you. Don't expect that you will always get the right type of data or even any data in a request from a user. And don't rely on client-side validation alone

router.route('/transfer-money').post((req,res)=>{constamount=req.body.amount;constfrom=user.id;constto=req.body.to;// What happens if we got a string instead of a number as our amount? This// function would failtransferMoney(from,to,amount);});

Security

Data security is the most important aspect of your application. If users can't trust you with their data, then you won't have a business. There are numerous different types of security exploits that can plague an app, depending on the particular language and runtime environment. Below is a very small and incomplete list of common security problems. Don't rely on this alone! Automate as much security review as you can on every commit, and perform routine security audits.

XSS should not be possible

Cross-site scripting (XSS), is one of the largest vectors for security attacks on a web application. It occurs when you take user data and include it in your page without first properly sanitizing it. This can cause your site to execute source code from remote pages.

function(){letbadge=document.getElementsByClassName('badge');letnameQueryParam=getQueryParams('name');/** * What if nameQueryParam was `<script>sendCookie(document.cookie)</script>`? * If that was the query param, a malicious user could lure a user to click a * link with that as the `name` query param, and have the user unknowingly * send their data to a bad actor. */badge.children[0].innerHTML=nameQueryParam;}

Personally Identifiable Information (PII) should not leak

You bear an enormous weight of responsibility every time you take in user data. If you leak data in URLs, in analytics tracking to third parties, or even expose data to employees that shouldn't have access, you greatly hurt your users and your business. Be careful with other people's lives!

router.route('/bank-user-info').get((req,res)=>{constname=user.name;constid=user.idconstsocialSecurityNumber=user.ssn;// There's no reason to send a socialSecurityNumber back in a query parameter// This would be exposed in the URL and potentially to any middleman on the// network watching internet trafficres.addToQueryParams({
name,
id,
socialSecurityNumber
})});

Performance

Functions should use efficient algorithms and data structures

This is different for every particular case, but use your best judgment to see if there are any ways to improve the efficiency of a piece of code. Your users will thank you for the faster speeds!

// If mentions was a hash data structure, you wouldn't need to iterate through// all mentions to find a user. You could simply return the presence of the// user key in the mentions hashfunctionisUserMentionedInComments(mentions,user){letmentioned=false;mentions.forEach(mention=>{if(mention.user===user){mentioned=true;}})returnmentioned;}

Important actions should be logged

Logging helps give metrics about performance and insight into user behavior. Not every action needs to be logged, but decide with your team what makes sense to keep track of for data analytics. And be sure that no personally identifiable information is exposed!

router.route('/request-ride').post((req,res)=>{constcurrentLocation=req.body.currentLocation;constdestination=req.body.destination;requestRide(user,currentLocation,destination).then(result=>{// We should log before and after this block to get a metric for how long// this task took, and potentially even what locations were involved in ride// ...});});

Testing

New code should be tested

All new code should include a test, whether it fixes a bug, or is a new feature. If it's a bug fix it should have a test proving that the bug is fixed. And if it's a new feature, then every component should be unit tested and there should be an integration test ensuring that the feature works with the rest of the system.

Tests should actually test all of what the function does

functionpayEmployeeSalary(employeeId,amount,callback){db.get('EMPLOYEES',employeeId).then(user=>{returnsendMoney(user,amount);}).then(res=>{if(callback){callback(res);}returnres;})}constcallback=(res)=>console.log('called',res);constemployee=createFakeEmployee('john jacob jingleheimer schmidt');constresult=payEmployeeSalary(employee.id,1000,callback);assert(result.status===enums.SUCCESS);// What about the callback? That should be tested

Tests should stress edge cases and limits of a function

functiondateAddDays(dateTime,day){// ...}letdateTime='1/1/2017'letdate1=dateAddDays(dateTime,5);assert(date1==='1/6/2017');// What happens if we add negative days?// What happens if we add fractional days: 1.2, 8.7, etc.// What happens if we add 1 billion days?

Miscellaneous

"Everything can be filed under miscellaneous"

George Bernard Shaw

TODO comments should be tracked

TODO comments are great for letting you and your fellow engineers that something needs to be fixed later. Sometimes you gotta ship code and wait to fix it later. But eventually you'll have to clean it up! That's why you should track it and give a corresponding ID from your issue tracking system so you can schedule it and keep track of where the problem is in your codebase.

Commit messages should be clear and accurately describe new code

We've all written commit messages like "Changed some crap", "damn it", "ugg one more to fix this stupid bug". These are funny and satisfying, but not helpful when you're up on a Saturday morning because you pushed code on a Friday night and can't figure out what the bad code was doing when you git blamed the commit. Write commit messages that describe the code accurately, and include a ticket number from your issue tracking system if you have one. That will make searching through your commit log much easier.

The code should do what it's supposed to do

This seems obvious, but most reviewers don't have the time or take the time to manually test every user-facing change. It's important to make sure the business logic of every change is as per design. It's easy to forget that when you're just looking for problems in the code!

About

🔬 Common problems to look for in a code review

Resources

Stars

0 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages