Perfect Code

  • View
    2.992

  • Download
    0

  • Category

    Software

Preview:

DESCRIPTION

Learn how to write better code. Follow key software development principles like KISS, DRY, YAGNI, and SOLID. Know how to choose better names, structure your code, write methods, and design classes.

Citation preview

Perfect Code!How to write high-quality code!

ARTEM TABALIN!

Motivation!Why should we always improve our code?!Why code that just works is not enough? !

Code Quality Control Law !

Improving code quality reduces development costs!

Why?!

Developer average productivity is!10-50 lines per day!

Writing code – 10%!Reading code, testing, debugging – 90%!

!

Productivity vs Time!

Time

Pro

du

ctiv

ity

Productivity decreases with entropy increasing!

Software Development Paradox!

1.  Low-quality code slows down development!

2.  Devs reduce code quality to meet a deadline!

Paradox: “Devs have no time to work rapidly”!

The clue: #2 is wrong!

Impossible to successfully meet a deadline messing up the code!

Continuously maintain high code quality!

The Boy Scout Rule!

It’s not enough to write code well - prevent “rotting”!

!

“Leave the campground cleaner than you found it”!!

Software Development Principles!KISS, YAGNI, DRY, SOLID!

Keep Code Simple!

KISS (Keep It Simple, Stupid)!

Simplicity is a key goal!

Components should be integrated tightly or weakly (?)!

Try to reduce influence between components!

Follow “Divide and Conquer” principle!

!

Division of Responsibilities!

•  System → packages!

•  Package → classes!

•  Class → methods!

•  Method → control statements!

Low coupling on each level!

Use Patterns!

Don’t reinvent the square wheel!

•  Reduce complexity!

•  Less mistakes!

•  Simplify communication!

!

Examples: Repository, Factory, Adapter, Strategy, Observer!

No Unnecessary Code!

YAGNI (You Aren’t Gonna Need It)!

Implement things only when you really need them!!

Avoid Duplication!

DRY (Don’t Repeat Yourself)!

!

“Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”!

!

Object-Oriented Design!

SOLID!

1.  Single Responsibility (SRP)!

2.  Open-Closed (OCP)!

3.  Liskov Substitution (LSP)!

4.  Interface Segregation (ISP)!

5.  Dependency Inversion (DIP)!

Single Responsibility Principle!

“A class should have one and only one reason to change”!!

Open-Closed Principle!Class should be opened to extension and closed for modification!

GraphicEditor

+drawShape

Shape

+draw

Rectangle

+draw

Circle

+draw

violation +drawCircle +drawRectangle +drawTriangle

Triangle

+draw

Liskov Substitution Principle!Objects implementing an interface can be used interchangeably!

!

IResizable +bounds +resize

Shape +bounds +resize

Image +bounds +resize

violation

resize(IResizable obj) {

if(obj is TextBlock)

return; obj.resize(); }

TextBlock +bounds +resize

GraphicEditor

+resize(IResizable)

Interface Segregation Principle!Clients should not depend on interfaces they don’t use!

!

Shape +resize +move +select

IResizable +resize

ISelectable +select

violation

IGraphic +resize +move +select

IMovable +move

Dependency Inversion Principle!Abstractions should not depend on details!

GraphicEditor

+logger: ILogger

ILogger

EmailLogger SystemLogger

violation

GraphicEditor() {

logger = new FileLogger(); }

FileLogger

Meaningful Names!Naming principles. Name length. Variable names. !Class names. Method names.!

Naming Principles!

Name should show your intention!

int  d;  //  elapsed  time  

Is it good enough?!

Which name is better?!

int  elapsedTimeInDays;  

int  daysSinceCreation;  

!

!

!

Naming Principles!What does the following code do?!

function  getItems(items)  {          var  list1  =  [];          for(var  i  =  0;  i  <  items.length;  i++)  {                  var  x  =  items[i];                  if(items[i]["Field"]  ===  12)  {                          list1.push(x);                  }          }          return  list1;  }  

!

Naming Principles!What is about this one?!

function  getMarkedCells(cells)  {          var  result  =  [];          for(var  i  =  0;  i  <  cells.length;  i++)  {                  var  cell  =  cells[i];                  if(cell.Status  ===  CellStatus.Marked)  {                          result.push(cell);                  }          }          return  result;  }  

Naming Principles!

No implementation details!

HashSet<Account>  accountHashSet;  

Is it good enough? Why?!

What if we decide to use List?!Which name is better?!

HashSet<Account>  accounts;  

!

Naming Principles!

Choose easy-to-pronounce name!

class  Cstmr  {          private  int  prsnid;          private  DateTime  genTstmp;          private  DateTime  modTstmp;  }  

class  Customer  {          private  int  personId;          private  DateTime  generationTimestamp;          private  DateTime  modificationTimestamp;  

}  

!

Naming Principles!

•  Single word for concept (avoid synonyms)! fetch / retrieve / get controller / manager / driver!

•  Names from patterns and algos! RenderStrategy, JobQueue!

•  Names from user stories! Credit, Debit, Asset!

•  Antonyms! begin / end first / last min / max next / previous! old / new opened / closed source / target up / down!!

Naming Convention!

The most important is to follow to the convention!

Example:!•  CONSTANT  

•  ClassName  

•  localVariable  

•  _privateMember  

Name Length!What is the optimal length for a name?!

x → maximumNumberOfPointsInEachLine  

short / long name pros & cons!

short names – lacks of information!long names – harder to read and write!!10-16 characters is the optimal length!if < 10 ask: is meaningful enough?!if > 16 ask: isn’t verbose?!

Loop Variable Names!

•  Simple loop!for(int  i  =  0;  i  <  data.length;  i++)  {          data[i]  =  0;  }  

•  Complex loop!for(int  teamIndex  =  0;  teamIndex  <  teamCount;  teamIndex++)  {          for(int  playerIndex  =  0;  playerIndex  <  count;  playerIndex++)  {                  playerStore[teamIndex][playerIndex]  =  0;          }  }!

Loop Variable Names!

•  Variable used outside of the loop  

int  recordCount  =  0;  

while(moreRecords())  {          data[recordCount]  =  getNextRecord();          recordCount++;  }  

//  …  recordCount  is  accessed  here  

!

Temporary Variable Names!

Avoid pointless names!

temp  =  sqrt(b^2  -­‐  4*a*c);  root[0]  =  (-­‐b  +  temp)  /  (2*a);  root[1]  =  (-­‐b  -­‐  temp)  /  (2*a);  

How to improve?!

discriminant  =  sqrt(b^2  -­‐  4*a*c);  root[0]  =  (-­‐b  +  discriminant)  /  (2*a);  root[1]  =  (-­‐b  -­‐  discriminant)  /  (2*a);  

!

Boolean Variables and Methods!

Use verbs is, has, can for booleans!

•  isSupported!

•  hasNext!

•  canExecute!

Class Names!

Use nouns and noun combinations for classes!

•  Client!

•  Window!

•  UserAccount!

•  FileLoader!

Method Names!

Use verbs and verb phrases for methods!

•  Save!

•  Close!

•  GetAccount!

•  LoadFile!

Variables!Declaration. Initialization. Lifetime and scope.!

Declaration!

•  Declare all variables!

•  Turn off implicit variable declaration!

JavaScript:  “use  strict”  

VB:  Option  Explicit  On  

php:  error_reporting(E_STRICT);  

!

Initialization!

Initialize variables at declaration!

var  total  =  0;  

or at first occurrence in code!

Dim  total  As  Double  '  another  code  total  =  0.0  '  code  using  total  

!

Lifetime and Scope!

Variables lifetime and scope should be minimal!

Why?!

Does code follow the principle?!

function  summarizeData()  {          var  oldData  =  getOldData();          var  totalOldData  =  summarize(oldData);          printData(totalOldData);          saveData(totalOldData);            var  newData  =  getNewData();          var  totalNewData  =  summarize(newData);          printData(totalNewData);          saveData(totalNewData);  }    

Lifetime and Scope!Code might look like!

function  summarizeData()  {          var  oldData  =  getOldData();          var  newData  =  getNewData();                    var  totalOldData  =  summarize(oldData);          var  totalNewData  =  summarize(newData);                    printData(totalOldData);          printData(totalNewData);                    saveData(totalOldData);          saveData(totalNewData);  }    

!

!

Lifetime and Scope!

•  Single purpose principle!int  value  =  getValue();  int  result  =  calcResult(value);  //  some  code  value  =  val1;      val1  =  val2;        val2  =  value;  

•  Avoid global variables!

•  Prefer the most restrictive access level! private → public!

var  swap  =  val1;  val1  =  val2;  val2  =  swap;    

Methods (Functions)!Purposes. Principles. Order. Parameters.!

Purposes!

•  Simplify code !

•  Improve readability !

•  Documentation defines abstraction!

•  Eliminating duplication most popular!

•  Inheritance support method overriding!

Principles!

Method should be small!

How many lines?!

not more than 20 lines and better even less!

Example:!

public  ReportContainer  BuildReport(IList<Order>  orders)  {          var  report  =  new  ReportContainer();          report.Lines  =  BuildReportLines(orders);          report.Total  =  BuildReportTotal(report.Lines);          return  report;  }  

!

Principles!

Method should have!

•  blocks (if, else, for, while) 1-3 lines long!•  indent level not more than 2!

private  IList<ReportLine>  BuildReportLines(IList<Order>  orders)  {          IList<ReportLine>  result  =  new  List<ReportLine>();          foreach  (Order  order  in  orders)  {                  ReportLine  reportLine  =  BuildReportLine(order);                  if  (reportLine.IsVisible)                          result.Add(reportLine);          }          return  result;  }  

!

Principles!

Single Thing Rule!

Function should do one thing, do it only, and do it well!

Examples:!

•  BuildReport – makes report!

•  BuildReportLines – prepares report lines!

•  BuildReportTotal – summarizes report!

Criteria: function cannot be divided into sections!

Principles!

Single Abstraction Level!

Mixing up abstraction levels increases complexity!

Does code follow the principle? Why?!

private  Report  BuildReport(Order  order)  {          var  result  =  new  Report();            result.Header  =  BuildReportHeader(order.Title);                    result.Body  =  ReportHelper.TextToHtml(order.Description);            result.Footer  =  "<b>"  +  APP_NAME  +  "</b>";          result.Footer  +=  "  CopyRight  ©  "  +  DateTime.Now.Year;            return  result;  }  

!

Principles!Better!

private  Report  BuildReport(Order  order)  {          var  result  =  new  Report();          result.Header  =  BuildReportHeader(order.Title);          result.Body  =  ReportHelper.TextToHtml(order.Description);          result.Footer  =  BuildReportFooter();          return  result;  }  

Much Better!

private  Report  BuildReport(Order  order)  {          var  result  =  new  Report();          result.Header  =  BuildReportHeader(order.Title);          result.Body  =  BuildReportBody(order.Description);          result.Footer  =  BuildReportFooter();          return  result;  }  

!

Principles!

Command-Query Separation (CQS)!

Method performs an action OR returns data!

Does code follow the principle?!

if  (SetAttribute("username",  "Admin"))  {          //  code  }  

How to fix?  

if  (HasAttribute("username"))  {          SetAttribute("username",  "Admin");          //  code  }    

Order!The Stepdown Rule!

Read code from top to bottom (by abstraction levels)!

public  Report  BuildReport(IList<Order>  orders)  {          //  uses  BuildReportLines  and  BuildReportTotal  }    private  Report  BuildReportLines(IList<Order>  orders)  {          //  uses  BuildReportLine  }    private  Report  BuildReportLine(IList<Order>  orders)  {          //  code  }    private  Report  BuildReportTotal(IList<Order>  orders)  {          //  code  }  

Arguments!How many parameters should a method have?!

•  Ideally 0!

•  Avoid methods with more than 2 arguments!

Why?!

•  Harder to read (remember passing values)!

•  Increase coupling!

•  Harder to test (combinatorial growth of variants)!

Arguments!Avoid boolean arguments!

Why?!

•  Usually can be divided into two sections!•  Hard to understand a purpose of an argument!BuildReportLine(order,  true);  

private  Report  BuildReportLine(Order  order,  bool  isMarked)  {          if  (isMarked)  {                  //  build  marked  report  line          }            else  {                  //  build  simple  report  line          }  }  

Classes!Purposes. Principles. Interface. Encapsulation. Inheritance.!

Purposes!•  Simplify code

•  Improve readability!

•  Limit the scope of changes!

•  Hide implementation details (encapsulation)!

•  Allow to prepare generic solution (abstraction)!

•  Eliminate duplication (delegation & inheritance)!

•  Provide extensibility (inheritance)!

•  Allow to work with real domain entities!

Principles!

•  Encapsulation! Hide as much as possible!

•  Compactness! Reduce class responsibilities (SRP)!

•  Abstraction! Program to an interface, not an implementation!

•  High Cohesion! Fields and methods should belong together!

Interface!

Methods should be on the same abstraction level!

Does code follow the principle? Why?!

class  Program  {          public  void  InitializeCommandStack();          public  void  PushCommand(Command  command);          public  Command  PopCommand();          public  void  ShutdownCommandStack();          public  void  InitializeReportFormatting();          public  void  FormatReport(Report  report);          public  void  PrintReport(Report  report);          public  void  InitializeGlobalData();          public  void  ShutdownGlobalData();  }  

Interface!

Programming over semantic!

•  Programming – can be checked by compiler! Amount of arguments! Argument types!

•  Semantic – cannot be checked by compiler! Method A should be called after method B! Method A throws exception when argument is not initialized!!

Encapsulation!

•  Not public should be private ! private → public!

•  No direct access to fields!private  decimal  _x  =  0;  public  decimal  X  {          get  {  return  _x;  }          set  {  _x  =  value;  }  }  

•  No assumptions about class clients!//  param2  should  be  >  0,  in  other  case  method  fails  public  int  Method(int  param1,  int  param2)  

!

Inheritance!

Should be able to say that child ‘is’ parent!Circle is Shape HtmlParser is Parser!

Does code follow the principle?!  class  Customer  {          public  int  ID  {  get;  set;  }          public  string  Name  {  get;  set;  }  }    class  Bank:  Customer  {          public  string  CorrespondentAccount  {  get;  set;  }  }  

Depends on whether a bank can be a customer or not!

!

Inheritance!

Move common methods to base class!

abstract  class  Stream  {          public  void  Write(byte[]  bytes);          public  byte[]  Read();  }      class  SecureStream:  Stream  {          public  void  Init(StreamConfig  config);  }  

!

Inheritance!

Avoid following situations:!

•  Parent class has only one child!

•  Child overrides method leaving it empty!

•  Too many hierarchy levels! better not more than 3 levels!

Comments!Commenting principles. Bad comments. Good comments.!

Commenting Principles!

•  Comments do not redress bad code! clean code much better than bad code with comments!

•  Do not comment tricky code – rewrite it! tricky code = bad code!

•  Try to explain with code!//  is  employee  eligible  for  bonus  if  ((employee.Schedule  ==  HOURLY_SCHEDULE)  &&          employee.Age  >  65)  {  …  }  !Better!

if  (employee.IsEligibleForBonus())  {  …  }  

Commenting Principles!

Comment says “why” not “how”!

•  How!//  if  flag  equals  to  zero  if  (accountFlag  ==  0)  

•  Why!//  if  new  account  created  if  (accountFlag  ==  0)  

•  Code instead of comment!if  (accountType  ==  AccountType.NewAccount)  

Bad Comments!•  Redundant comments! obvious and repeating code!

//  closing  connection  Connection.Close();    

•  Commented code! remove unnecessary code, VCS is for the rescue  

InputStream  response  =  new  InputStream();  response.SetBody(formatter.ResultStream,  formatter.BytesCount);  //InputStream  resultStream  =  formatter.ResultStream;  //StreamReader  reader  =  new  StreamReader(resultStream);  //response.SetContent(reader.Read(formatter.BytesCount));  

Good Comments!•  Legal comments! license, copyright, author!

•  Explanations! provide info about inevitable tricks  

//  NOTE:  postpone  execution  to  refresh  UI  setTimeout(function()  {          //  code  });  

•  TODOs! notes for future to avoid “broken windows”  

//  TODO:  get  rid  of  tricky  workaround  function  badFunction()  {          //  tricky  workaround  here  });  

Thank you!!

Your questions, please!!ARTEM TABALIN!

References!

•  Steve McConnell Code Complete!

•  Robert C. Martin Clean Code!

•  Hunt A., Thomas D. The Pragmatic Programmer !

•  Craig Larman Applying UML and Patterns!

•  Kent Beck Implementation Patterns!

•  Eric Evans Domain-Driven Design!

Recommended