Lunch & Learn - Clean Code

June 17, 2016

  • What is clean code?

  • Why is clean code so important?

  • Hands on

  • How to write cleaner code?

Agenda

http://xkcd.com/844/

What is clean code?

What is clean code?

  • Reads like well-written prose
  • Clarifies the designer's intent
  • Sustains simple and direct modules
  • Preserves straightforward logical flow
  • Employs efficient abstractions
  • Fosters maintenance and enhancement
  • Provides one way to do one thing
  • Presents a clear and minimal API
  • Includes unit and acceptance tests

What is clean code?

  • Reads like well-written prose
  • Clarifies the designer's intent
  • Sustains simple and direct modules
  • Preserves straightforward logical flow
  • Employs efficient abstractions
  • Fosters maintenance and enhancement
  • Provides one way to do one thing
  • Presents a clear and minimal API
  • Includes unit and acceptance tests

Clean code is the reward for elegant design, planning, and execution.

Why is clean code so important?

In particular - why is clean code so important at this stage of your career? 

As a student:

  • Solve it
  • Test it
  • Fix it
  • Turn it in
  • Forget about it

Why is clean code so important?

As a student:

  • Solve it
  • Test it
  • Fix it
  • Turn it in
  • Forget about it

As a professional:

  • Solve it
    • Make it easy to understand
    • Make it easy to use
    • Make it easy to change
  • Test it
  • Fix it
  • Review it
  • Commit it
  • Test it
  • Release it
  • Support it

Why is clean code so important?

#!/usr/bin/python
 import os
import sys
import csv
import datetime
import time
import twitter

def test():

        #run speedtest-cli
        print 'running test'
        a = os.popen("python /home/pi/speedtest/speedtest-cli --simple").read()
        print 'ran'
        #split the 3 line result (ping,down,up)
        lines = a.split('\n')
        print a
        ts = time.time()
        date =datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
        #if speedtest could not connect set the speeds to 0
        if "Cannot" in a:
                p = 100
                d = 0
                u = 0
        #extract the values for ping down and up values
        else:
                p = lines[0][6:11]
                d = lines[1][10:14]
                u = lines[2][8:12]
        print date,p, d, u
        #save the data to file for local network plotting
        out_file = open('/var/www/assets/data.csv', 'a')
        writer = csv.writer(out_file)
        writer.writerow((ts*1000,p,d,u))
        out_file.close()

        #connect to twitter
        TOKEN=""
        TOKEN_KEY=""
        CON_SEC=""
        CON_SEC_KEY=""

        my_auth = twitter.OAuth(TOKEN,TOKEN_KEY,CON_SEC,CON_SEC_KEY)
        twit = twitter.Twitter(auth=my_auth)

        #try to tweet if speedtest couldnt even connet. Probably wont work if the internet is down
        if "Cannot" in a:
                try:
                        tweet="Hey @Comcast @ComcastCares why is my internet down? I pay for 150down\\10up in Washington DC? #comcastoutage #comcast"
                        twit.statuses.update(status=tweet)
                except:
                        pass

        # tweet if down speed is less than whatever I set
        elif eval(d)<50:
                print "trying to tweet"
                try:
                        # i know there must be a better way than to do (str(int(eval())))
                        tweet="Hey @Comcast why is my internet speed " + str(int(eval(d))) + "down\\" + str(int(eval(u))) + "up when I pay for 150down\\10up in Washington DC? @ComcastCares @xfinity #comcast #speedtest"
                        twit.statuses.update(status=tweet)
                except Exception,e:
                        print str(e)
                        pass
        return
        
if __name__ == '__main__':
        test()
        print 'completed'
import os
import sys
import time
from datetime import datetime
import daemon
import signal
import threading
import twitter
import json 
import random
from logger import Logger

shutdownFlag = False

def main(filename, argv):
    print "======================================"
    print " Starting Speed Complainer!           "
    print " Lets get noisy!                      "
    print "======================================"

    global shutdownFlag
    signal.signal(signal.SIGINT, shutdownHandler)

    monitor = Monitor()

    while not shutdownFlag:
        try:

            monitor.run()

            for i in range(0, 5):
                if shutdownFlag:
                    break
                time.sleep(1)

        except Exception as e:
            print 'Error: %s' % e
            sys.exit(1)

    sys.exit()

def shutdownHandler(signo, stack_frame):
    global shutdownFlag
    print 'Got shutdown signal (%s: %s).' % (signo, stack_frame)
    shutdownFlag = True

class Monitor():
    def __init__(self):
        self.lastPingCheck = None
        self.lastSpeedTest = None

    def run(self):
        if not self.lastPingCheck or (datetime.now() - self.lastPingCheck).total_seconds() >= 60:
            self.runPingTest()
            self.lastPingCheck = datetime.now()

        if not self.lastSpeedTest or (datetime.now() - self.lastSpeedTest).total_seconds() >= 3600:
            self.runSpeedTest()
            self.lastSpeedTest = datetime.now()

    def runPingTest(self):
        pingThread = PingTest()
        pingThread.start()

    def runSpeedTest(self):
        speedThread = SpeedTest()
        speedThread.start()

class PingTest(threading.Thread):
    def __init__(self, numPings=3, pingTimeout=2, maxWaitTime=6):
        super(PingTest, self).__init__()
        self.numPings = numPings
        self.pingTimeout = pingTimeout
        self.maxWaitTime = maxWaitTime
        self.config = json.load(open('./config.json'))
        self.logger = Logger(self.config['log']['type'], { 'filename': self.config['log']['files']['ping'] })

    def run(self):
        pingResults = self.doPingTest()
        self.logPingResults(pingResults)

    def doPingTest(self):
        response = os.system("ping -c %s -W %s -w %s 8.8.8.8 > /dev/null 2>&1" % (self.numPings, (self.pingTimeout * 1000), self.maxWaitTime))
        success = 0
        if response == 0:
            success = 1
        return { 'date': datetime.now(), 'success': success }

    def logPingResults(self, pingResults):
        self.logger.log([ pingResults['date'].strftime('%Y-%m-%d %H:%M:%S'), str(pingResults['success'])])

class SpeedTest(threading.Thread):
    def __init__(self):
        super(SpeedTest, self).__init__()
        self.config = json.load(open('./config.json'))
        self.logger = Logger(self.config['log']['type'], { 'filename': self.config['log']['files']['speed'] })

    def run(self):
        speedTestResults = self.doSpeedTest()
        self.logSpeedTestResults(speedTestResults)
        self.tweetResults(speedTestResults)

    def doSpeedTest(self):
        # run a speed test
        result = os.popen("/usr/local/bin/speedtest-cli --simple").read()
        if 'Cannot' in result:
            return { 'date': datetime.now(), 'uploadResult': 0, 'downloadResult': 0, 'ping': 0 }

        # Result:
        # Ping: 529.084 ms
        # Download: 0.52 Mbit/s
        # Upload: 1.79 Mbit/s

        resultSet = result.split('\n')
        pingResult = resultSet[0]
        downloadResult = resultSet[1]
        uploadResult = resultSet[2]

        pingResult = float(pingResult.replace('Ping: ', '').replace(' ms', ''))
        downloadResult = float(downloadResult.replace('Download: ', '').replace(' Mbit/s', ''))
        uploadResult = float(uploadResult.replace('Upload: ', '').replace(' Mbit/s', ''))

        return { 'date': datetime.now(), 'uploadResult': uploadResult, 'downloadResult': downloadResult, 'ping': pingResult }

    def logSpeedTestResults(self, speedTestResults):
        self.logger.log([ speedTestResults['date'].strftime('%Y-%m-%d %H:%M:%S'), str(speedTestResults['uploadResult']), str(speedTestResults['downloadResult']), str(speedTestResults['ping']) ])


    def tweetResults(self, speedTestResults):
        thresholdMessages = self.config['tweetThresholds']
        message = None
        for (threshold, messages) in thresholdMessages.items():
            threshold = float(threshold)
            if speedTestResults['downloadResult'] < threshold:
                message = messages[random.randint(0, len(messages) - 1)].replace('{tweetTo}', self.config['tweetTo']).replace('{internetSpeed}', self.config['internetSpeed']).replace('{downloadResult}', str(speedTestResults['downloadResult']))

        if message:
            api = twitter.Api(consumer_key=self.config['twitter']['twitterConsumerKey'],
                            consumer_secret=self.config['twitter']['twitterConsumerSecret'],
                            access_token_key=self.config['twitter']['twitterToken'],
                            access_token_secret=self.config['twitter']['twitterTokenSecret'])
            if api:
                status = api.PostUpdate(message)

class DaemonApp():
    def __init__(self, pidFilePath, stdout_path='/dev/null', stderr_path='/dev/null'):
        self.stdin_path = '/dev/null'
        self.stdout_path = stdout_path
        self.stderr_path = stderr_path
        self.pidfile_path = pidFilePath
        self.pidfile_timeout = 1

    def run(self):
        main(__file__, sys.argv[1:])

if __name__ == '__main__':
    main(__file__, sys.argv[1:])

    workingDirectory = os.path.basename(os.path.realpath(__file__))
    stdout_path = '/dev/null'
    stderr_path = '/dev/null'
    fileName, fileExt = os.path.split(os.path.realpath(__file__))
    pidFilePath = os.path.join(workingDirectory, os.path.basename(fileName) + '.pid')
    from daemon import runner
    dRunner = runner.DaemonRunner(DaemonApp(pidFilePath, stdout_path, stderr_path))
    dRunner.daemon_context.working_directory = workingDirectory
    dRunner.daemon_context.umask = 0o002
    dRunner.daemon_context.signal_map = { signal.SIGTERM: 'terminate', signal.SIGUP: 'terminate' }
    dRunner.do_action()

How to write cleaner code?

  1. Naming conventions
  2. Modularity

What significant changes were made between the first piece of code and the second piece of code? 

Why are naming conventions important?

If you can't come up with a good name for it, then it's a mess. The names are the API talking back to you, so listen to them.

- Joshua Bloch

The ratio of time spent reading vs. writing is well over 10:1. We are constantly reading old code as part of the effort to write new code.

- Robert C. Martin

Naming Conventions - Some Simple Examples

 Reveal Intention

int d; // elapsed time in days
int daysSinceCreation;

Avoid disinformation

Account[] accountList;
boolean notActive;

Make meaningful distinctions

void arrayCopy(char[] a1, char[] a2);
void arrayCopy(char[] source, char[] destination);

Pronounceable

String evtStCd, evtAudtg;

Searchable

Date date, transactionDate;
public class RequestBuilder { ...

Solution/Problem relevant

int tableUsage, loadFactor;
Node tortoise, hare;

Coupling and Cohesion

Coupling is a measure of how closely connected two routines or modules are.

Cohesion is a measure of how strongly related each piece of functionality is.

Coupling and Cohesion have an inverse relationship.

With tightly coupled systems: 

  • a change in one module forces changes in the others.

  • modules are harder to reuse.

  • modules are harder to test.

With low cohesion systems: 

  • modules are complex with more operations.

  • modules are less maintainable and harder to work with.

Orthogonality

Two or more modules are orthogonal if

changes in one do not affect the other.

  • When components are highly interdependent, there is no such thing as a quick, local fix.
     

  • Orthogonal software provides increased productivity and decreased risk because developers never have to worry about side-effects of making changes.
     

  • Orthogonal components are easier to swap meaning less dependence on a specific library or vendor.

Lunch & Learn - Clean Code

By dyanos91

Lunch & Learn - Clean Code

June 17, 2016

  • 363