Writing Clean Code

Styling

 “Programs must be written for people to read, and only incidentally for machines to execute."

- Harold Abelson, Structure and Interpretation of Computer Programs

One rule to rule them all

The 80 char rule

no 80 char

top_result = df.sort_values('age', ascending=True).groupby('city').head(int(top_item.limit))

80 char

top_result = (df
    .sort_values('age', ascending=True)
    .groupby('city')
    .head(int(top_item.limit))
)

Readability Counts

if (company.name == 'Orcablue' and company.domain == 'tech' and session is not None and cache is not None):
    # do something
if ((company.name == 'Orcablue') and 
    (company.domain == 'tech') and 
    (session is not None) and 
    (cache is not None)):
    # do something

Split up expressions into multiple lines

Readability Counts

def adder(a, b):
    return a + b
    
net_pay = adder(90 if employee.designation == 'Manager' else 100, employee.bonus if employee.bonus and employee.bonus > 0 else 0)
def adder(a, b):
    return a + b

salary = 90 if employee.designation == 'Manager' else 100
bonus = employee.bonus if employee.bonus and employee.bonus > 0 else 0
net_pay = adder(salary, bonus)

Do not pass expressions as parameters

Readability Counts

def check_if_measure(parse_node, tree_nodes):
    result = False
    ops = None
    parse_node_i = None
    for i, node in enumerate(tree_nodes):
        if node == parse_node:
            parse_node_i = i
    if parse_node_i and parse_node_i > 0:
        if tree_nodes[parse_node_i - 1].c_tag == '0' 
            result = True
            ops = parse_node.parent.t_tag
    return result,ops
def check_if_measure(parse_node, tree_nodes):
    result = False
    ops = None
    parse_node_i = None

    for i, node in enumerate(tree_nodes):
        if node == parse_node:
            parse_node_i = i

    if parse_node_i and parse_node_i > 0:
        if tree_nodes[parse_node_i - 1].c_tag == '0' 
            result = True
            ops = parse_node.parent.t_tag

    return result,ops

Split logic into visual contexts

Readability Counts

  • Number of lines of code matter much less than readability.
     
  • Split up multiple expressions into multiple lines
     
  • Do not pass expressions as parameters
     
  • Split up logic into visual contexts

Naming

class Person:
    def __init__(self, field1, field2, field3, field3):
        self.field1 = field1
        self.field2 = field2
        self.field3 = field3
        self.field4 = field4
class Employee:
    def __init__(self, name, age, designation, salary):
        self.name = name
        self.age = age
        self.designation = designation
        self.salary = salary

Naming

class Akira:
    def __init__(self, org_name, org_domain, created):
        self.org_name = org_name
        self.org_domain = org_domain
        self.created = created
        self.employees = {}
        self.engagements = {}

    def add_employee(self, p):
        self.employees[p.field1] = p

    def new_engagement(self, name, c, emp):
        eng_status = 'BEGIN'
        self.engagements[name] = (c, e, eng_status)

    def engagement_over(self, name):
        eng_status = 'TERMINATED'
        c, e, _ = self.engagements[name]
        self.engagements[engagement_name] = (c, e, eng_status)

    def expenditure(self):
        temp = 0
        for e in self.employees:
            temp += e.field4
        return temp

    def unassigned_employees(self):
        unassigned = []
        for e in self.employees:
            for p_name, p in self.projects.items():
                if p[1].field1 == e.field1:
                    unassigned.append(e)
        return unassigned

class Organization:
    def __init__(self, name, domain, incorporated):
        self.name = name
        self.domain = domain
        self.incorporated = incorporated
        self.employees = {}
        self.projects = {}

    def hire(self, employee):
        self.employees[employee.name]] = employee

    def start_project(self, project_name, client, employee):
        status = 'ONGOING'
        self.projects[project_name] = (client, employee, status)

    def finish_project(self, project_name):
        status = 'FINISHED'
        client, employee, _ = self.projects[project_name]
        self.projects[project_name] = (client, employee, status)

    def calculate_expenditure(self):
        sum_of_salaries = 0
        for employee in self.employees:
            sum_of_salaries = employee.salary
        return sum_of_salaries

    def get_unassigned_employees(self):
        assigned_employees = []

        for (_, employee, _) in self.projects.values():
            assigned_employees.append(employee)

        unassigned_employees = (
            set(self.employees) - set(assigned_employees))

        return unassigned_employees


Naming

  • Rumpelstiltskin Principle
    "When you give a name to something, you gain power over it"
     
  • Is something VS Does something
    Nouns for objects. Verbs for functions and methods.
     
  • Self documenting code
    Use long, explanatory variable names if they make the logic
    clearer.
     
  • Avoid generic names
    holder, value, field, data, my_list, single letter variables, var etc.

Code Smells

 “I like my code to be elegant and efficient."

- Bjarne Stroustrup, creator of C++

Code Smells

  • Overused conditional branches
    Lots of If-else blocks, whether nested or sequential.
     
  • DRY
    Don't Repeat Yourself. If some logic is getting repeated
    more than twice, it might be a good candidate for refactoring.
     
  • Jack of all trades functions and methods
    One function should do one thing. If complexity of logic, it
    should be abstracted away to other functions.
     
  • Passing expressions as arguments to functions

Code Smells

  • God Object
    A class or function that is doing too many things and has grown too large.
     
  • Feature Envy
    A class that uses methods of another class excessively.
     
  • The Good Samaritan
    A class or a function that is handling logic that should be handled by another class or function.
     
  • Shotgun Surgery
    The same change needs to be made in multiple places.

Unix Philosophy

  • Write programs that do one thing and do it well.
     
  • Write programs to work together.
     
  • Write programs to handle text streams, because that is a universal interface.

Debugging

Debugging

  • print statements
     
  • debuggers - pdb, ipython.embed
     
  • logging

Logging

  • Log liberally - Drastically reduces debugging effort.
     
  • Log handlers
     
  • Log Levels - Error, Info, Warn, Debug
     
  • Python - %s vs %r

Git Good Practices

Commits

  • Commit often - commits should be small and related.
     
  • Make commit messages as explanatory as possible.
     
  • Commit messages should ideally begin with verbs -
    Fixes, Adds, Changes, Refactors, Deletes, Removes, etc.

     
  • Try and tell why the commit was made in the commit message.
     
  • It's OK to commit very small changes with a single commit. It's NOT OK to commit very large changes with a single commit.

Feature Branches

# On master branch
git checkout -b new_feature

...

# On new_feature branch

git add ...
git commit -m "Adds skeleton for new class"

git add ...
git commit -m "Adds methods for conncting to database"

git add ...
git commit -m "Fixes issue #71. None return value needed to be handled."

git checkout master

# On master branch
git merge new_feature

git branch -d new_feature

git push

Push and PULL

  • Always pull before pushing your code
     
  • git pull --rebase OR git pull && git rebase
     
  • Handling merge conflicts

Fixing Commit Mistakes

  • git commit --amend
     
  • git commit --amend --no-edit

Writing Clean Code

By sngsahil

Writing Clean Code

  • 588