Top Banner
Clean code Roy Nitert 18-6-2013 GEWIS lunchlezing
27
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Clean code

Clean code

Roy Nitert

18-6-2013

GEWIS lunchlezing

Page 2: Clean code

© Sioux 2012 | Confidential | 2

Clean code agenda

Quality Readability Code smells Clean code characteristics

Single responsibility principle Code comments Small methods Descriptive names

Applying clean code

Page 3: Clean code

© Sioux 2012 | Confidential | 3

Quality

Poor SW quality cost $500 billion per year Customer expectation, free of deficiencies,

easy to use Improve quality: testing, requirements,

documentation, code reviews Studies have shown that poor readability

correlates strongly with defect density.

Page 4: Clean code

© Sioux 2012 | Confidential | 4

Requirements

Page 5: Clean code

© Sioux 2012 | Confidential | 5

Readability

To make it easier to understand and cheaper to modify (maintainability) software

The code you write will be read more times than it was written and by more people.

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” - Martin Fowler

Page 6: Clean code

© Sioux 2012 | Confidential | 6

Recognizing clean code

Page 7: Clean code

© Sioux 2012 | Confidential | 7

Code smells

A ‘smell’ in code is a hint that something might be wrong with the code Duplicated code Long switch/if statements Long methods Poor method/variable names In-line comments Large classes

Page 8: Clean code

© Sioux 2012 | Confidential | 8

What is clean code ?

Characteristics of clean code: Small methods and classes Descriptive names Not too many parameters in methods (and no flags) No obvious or irrelevant comments (code must be

self describing) No redundancy (DRY: don’t repeat yourself) No magic numbers Single responsibility principle (SRP) Uniform coding style

Page 9: Clean code

© Sioux 2012 | Confidential | 9

SRP: Single Responsibility Principle

every object should have a single responsibility, and that responsibility should be entirelyencapsulated by the class.

a class or module should haveone, and only one, reason to change

If class names have ‘manager’ or ‘processor’ in them, they probably have multiple responsibilities

the "S" in "SOLID" stands for the single responsibility principle (“Principles Of OOD”, Robert C. Martin)

Page 10: Clean code

© Sioux 2012 | Confidential | 10

Comments

Page 11: Clean code

© Sioux 2012 | Confidential | 11

Comments

Comments do not make up for bad code Good: Legal, explanation, TODO Bad: redundant, misleading, journal, headers Don’t use a comment when you can use a

function or a variable

// Returns an instance of the Responder being tested.protected abstract Responder responderInstance();

protected abstract Responder responderBeingTested();

Page 12: Clean code

© Sioux 2012 | Confidential | 12

Small methods

They should be very very small Should do one thing, do it well, do it only One level of abstraction Should read like a narrative Should not have sections Blocks within if, else and while

statements should be one line long

Page 13: Clean code

© Sioux 2012 | Confidential | 13

/*---------------------------------------------------------------------------*/

int PO_rq_do_service( PO_object_t *object_p, DDXA_var_string_t *document_type, DDXA_var_string_t *service, DDXA_var_string_t *document_path, DDXA_var_string_t *input_file_path, DDXA_var_string_t *output_file_path, ASML_bool use_compression, PLXAtimestamp *time_stamp, POXA_var_string_array_t *parameters, DDXA_var_string_t *user_name, POXA_DocMetaDataListType *list, POXA_var_string_array_t *error_messages )/* * Description : Execute the requested service. The requested service is specified in * the 'service' parameter. The following services are defined: * put (=download), get (=upload), list, verify, and delete). * * Input(s) : document_type - type of document ( e.g. ADELfoo ) * service - Name of the service (get, put, verify, delete, list) * document_path - Service = get, delete, list: * Document identification (e.g. path/to/folder/foo.xml) * - Service = put, verify: * Not used. * input_file_path - Service = put, verify: * Name of XML formatted file (path+name) * - Service = get, delete, list * Not used. * output_file_path - Service = put, verify * Name of the XML formatted report file (path+name) * - Service = get * Name of the XML formatted file (path+name) * - Service = list * Name of the XML formatted ADELdirectory file (path+name) * (for Y* components using generic POxSERVICE interface) * - Service = delete * Not used * use_compression - Flag to indicate incoming document must unzipped and/or * outgoing document must be zipped. * time_stamp - Service = get * Time stamp of subrecipe to get (optional). * Service = put, verify, delete, list * Not used. * parameters - Array of strings pairs containing all the options and * values from the URL query string. * user_name - Name of an authenticated user * * Output(s) : list - Service = list * List of document names found, including meta data (used * for old Y* components NOT using generic POxSERVICE * interface) * - Service = put, get, verify, delete * Not used * error_messages - Array of error messages. * * Returns : OK * POXA_PARAM_ERR; * POXA_INVALID_TYPE; * POXA_ERR_RECIPE_DOES_NOT_EXIST; * POXA_SYS_ERR; * * Notes : The implementation of this function will have the following limitations: * - UserName: The UserName will be used for a user authorization mechanism. * This mechanism will not be implemented, the parameter will be ignored. * - ErrorMessages: The array of error messages will remain empty. * * Pre.Cond. : * * Post.Cond. : */{ int r = OK; int r_unzip = OK; ASML_bool single_zip = FALSE; ASML_bool is_secs = FALSE; POXA_DataType data_type = POXA_DATA_TYPE_UNDEFINED; PODS_SERVICE_ENUM service_value = PODS_SERVICE_UNKNOWN; char type_name[PODS_MAX_TYPE_LENGTH] = "";

if (( r == OK) && (object_p == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, object_p = NULL")); }

if (( r == OK) && (document_type == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, document_type = NULL")); }

if (( r == OK) && (service == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, service = NULL")); }

if (( r == OK) && (document_path == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, document_path = NULL")); }

if (( r == OK) && (input_file_path == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, input_file_path = NULL")); }

if (( r == OK) && (output_file_path == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, output_file_path = NULL")); }

if ((r == OK) && (time_stamp == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, time_stamp = NULL")); }

if (( r == OK) && (parameters == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, parameters = NULL")); }

if (( r == OK) && (user_name == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, user_name = NULL")); }

if (( r == OK) && (list == NULL)) { r = PO_ERR_NULL_PARAMETER; ERXA_LOG(r, OK, ("Parameter Error, list = NULL")); }

if (( r == OK) && (error_messages == NULL)) { r = PO_ERR_NULL_PARAMETER;

Long function

if ( (r == OK) && ( strcmp( document_type->buf, "" ) == 0 ) ) { is_secs = TRUE; }

if (r == OK) { THXAtrace (CC, THXA_TRACE_INT, __func__, "> (%D, %D, %D, %D, %D, use_compression=%s, %D, %D, %D)", "document_type", "varstring", document_type, "service", "varstring", service, "document_path", "varstring", document_path, "input_file_path", "varstring", input_file_path, "output_file_path", "varstring", output_file_path, (use_compression) ? "yes" : "no", "time_stamp", "timestamp", time_stamp, "parameters", POXA_VAR_STRING_ARRAY_T_STR, parameters, "user_name", "varstring", user_name ); }

if (r == OK) { if ( strcmp(service->buf, POXA_SERVICE_GET_STRING) == 0 ) { if ( data_type == POXA_DATA_TYPE_FILE ) { service_value = PODS_SERVICE_GET_FILE_PATH; } else { service_value = PODS_SERVICE_GET; } } else if ( strcmp(service->buf, POXA_SERVICE_PUT_STRING) == 0 ) { service_value = PODS_SERVICE_PUT; } else if ( strcmp(service->buf, POXA_SERVICE_VERIFY_STRING) == 0 ) { service_value = PODS_SERVICE_VERIFY; } else if ( strcmp(service->buf, POXA_SERVICE_DELETE_STRING) == 0 ) { service_value = PODS_SERVICE_DELETE; } else if ( strcmp(service->buf, POXA_SERVICE_LIST_STRING) == 0 ) { service_value = PODS_SERVICE_LIST; } else { service_value = PODS_SERVICE_UNKNOWN; } }

if (r == OK) { if ( ( service_value == PODS_SERVICE_PUT) || ( service_value == PODS_SERVICE_VERIFY) ) { /* If compressed file sent source_xml_filename contains the filename of * xml document and can be anything. Type of document must be retreived * from the contents of the document itself. * In case a multi zip file is in the input_file, this call * will result in NOK, therefore result is directed to different result * variable (r_unzip) */ if ( is_secs ) { if ( use_compression ) { r_unzip = POGN_unzip_file( input_file_path->buf ); if (r_unzip == OK) { single_zip = TRUE; } }

/* Get document type from the file. */ r = PODS_get_type_from_doc_file( input_file_path->buf, type_name );

if (r == OK) { r = DDXA_var_string_alloc( document_type, type_name ); }

/* In case of unzip the file will need to be zipped */ if ( single_zip ) { r_unzip = POGN_zip_file( input_file_path->buf ); } } } else if ( (service_value == PODS_SERVICE_LIST) || (service_value == PODS_SERVICE_GET) || (service_value == PODS_SERVICE_GET_FILE_PATH) || (service_value == PODS_SERVICE_DELETE) ) { if ( is_secs ) { /* 'document_type' is empty string -> Document is sent via SX. */ r = PODS_extract_type_from_doc_id( document_path->buf, type_name );

if (r == OK) { r = DDXA_var_string_cat( document_type, type_name ); } else { ERXA_LOG(PO_ERR_SYS_ERROR, r, ("Error: Cannot extract document type and path from '%s'", document_path->buf)); r = PO_ERR_SYS_ERROR; } } } else { ERXA_DEACT( OK, PO_ERR_SYS_ERROR, ( "Unknown service requested." ) ); } }

if ( r == OK ) { if ( (service_value == PODS_SERVICE_GET) || (service_value == PODS_SERVICE_GET_FILE_PATH) ) { r = po_rq_handle_service_get( PO_RQ_ACTION_UPLOAD, document_type, document_path, input_file_path, output_file_path, time_stamp, parameters); } else if ( service_value == PODS_SERVICE_PUT ) { r = po_rq_handle_service_put_verify( PO_RQ_ACTION_DOWNLOAD, document_type, input_file_path,

/*---------------------------------------------------------------------------*/PO_RQ_ACTION_DOWNLOAD, document_type, input_file_path, output_file_path, use_compression, parameters, TRUE, single_zip); } else if ( service_value == PODS_SERVICE_VERIFY ) { r = po_rq_handle_service_put_verify( PO_RQ_ACTION_VERIFY, document_type, input_file_path, output_file_path, use_compression, parameters, FALSE, single_zip); } else if ( service_value == PODS_SERVICE_DELETE ) { r = po_rq_handle_service_delete( document_type, document_path, input_file_path, parameters); } else if ( service_value == PODS_SERVICE_LIST ) { r = po_rq_handle_service_list( document_type, document_path, parameters, output_file_path, list); } else { r = PO_ERR_SYS_ERROR; ERXA_LOG(r, OK, ("Error: unsupported service '%s'", service->buf)); } }

/* Zip the target file if necessary (put, verify and get). */ if ( ( r == OK ) && use_compression && ( ( service_value == PODS_SERVICE_GET ) || ( service_value == PODS_SERVICE_GET_FILE_PATH ) || ( service_value == PODS_SERVICE_PUT ) || ( service_value == PODS_SERVICE_VERIFY ) ) ) { if (single_zip) { r = POGN_zip_file(output_file_path->buf); } }

/* create POXA errorcode from internal error codes */ if (r != OK) { int l = r; int r_pr = OK; char message[ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE]= "";

/* create POXA errorcode from internal error codes */ switch (l) { case PO_ERR_NULL_PARAMETER: r = POXA_PARAM_ERR;

r_pr = PLXAstr_strlcpy(message, "NULL parameter passed.", ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE); break;

case PO_ERR_INVALID_TYPE: r = POXA_INVALID_TYPE;

r_pr = PLXAstr_snprintf(message, ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE, "Invalid type '%s' provided.", document_type->buf); break;

case PO_ERR_NO_INSTANCE_ID_FOUND: case PO_ERR_OPENING_FILE: r = POXA_ERR_RECIPE_DOES_NOT_EXIST;

r_pr = PLXAstr_snprintf(message, ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE, "File '%s' does not exist.", input_file_path->buf); break;

case PO_ERR_GETTING_LIST: case PO_ERR_GETTING_DATA: case PO_ERR_STORING_DATA: case PO_ERR_PUTTING_DATA: case PO_ERR_VERIFYING_DATA: case PO_ERR_DELETING_DATA: r = POXA_SERVICE_FAILED;

r_pr = PLXAstr_snprintf(message, ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE, "Service '%s' failed for document type '%s'.", service->buf, document_type->buf); break;

case PO_ERR_SYS_ERROR: default: r = POXA_SYS_ERR;

r_pr = PLXAstr_strlcpy(message, "Internal error occurred.", ADELserviceResponse_MAX_MESSAGE_TYPE_SIZE); break; }

if (r_pr == OK) { if ( ( !is_secs ) && ( strcmp( output_file_path->buf, "" ) != 0) ) { int r_csr = OK;

r_csr = po_rq_create_service_response(output_file_path->buf, ADELserviceResponse_Error, r, message); if (r_csr != OK) { ERXA_DEACT( POXA_SYS_ERR, r_csr, ("") ); } } } else { ERXA_DEACT( POXA_SYS_ERR, r_pr, ("") ); }

ERXA_LOG(r, l,

PO_RQ_ACTION_DOWNLOAD, ("Error: Execution of service '%s' with document_type '%s' failed", service->buf, document_type->buf) ); }

THXAtrace (CC, THXA_TRACE_INT, __func__, "< (%D, %D) = %R", "list", POXA_DOCMETADATALISTTYPE_STR, list, "error_messages", POXA_VAR_STRING_ARRAY_T_STR, error_messages, r);

return r;}

Page 14: Clean code

© Sioux 2012 | Confidential | 14

Descriptive names

Names make software readable Helps clarify the design in your mind Use long names for long scopes Avoid encodings

Page 15: Clean code

© Sioux 2012 | Confidential | 15

Descriptive names example

Public int x() {

int q = 0;

int z = 0;

for (int kk = 0; kk < 10; kk++) {

if (l[z] == 10) {

q += 10 + (l[z + 1] + l[z + 2]);

z += 1;

} else if (l[z] + l[z + 1] == 10) {

q += 10 + l[z + 2];

z += 2;

} else {

q += l[z] + l[z + 1];

z += 2;

}

}

return q;

}

Page 16: Clean code

© Sioux 2012 | Confidential | 16

Descriptive names example

Public int score() {

int score = 0;

int frame = 0;

for (int frameNumber = 0; frameNumber < 10; frameNumber++) {

if (isStrike(frame)) {

score += 10 + nextTwoBallsForStrike(frame);

frame += 1;

} else if (isSpare(frame)) {

score += 10 + nextBallForSpare(frame);

frame += 2;

} else {

score += TwoBallsInFrame(frame);

frame += 2;

}

}

return score;

}

Page 17: Clean code

© Sioux 2012 | Confidential | 17

Parameters / arguments

Small number: Zero is best, followed by one, two or three

More than three is questionable Output arguments are counterintuitive Boolean arguments (flags) loudly declare than

the function does more than one thing Simpler is easy to understand and easy to test Reduce number of arguments by creating

objects out of them

Page 18: Clean code

© Sioux 2012 | Confidential | 18

Parameters example

Circle makeCircle(double x, double y, double radius);

public void AlignTaskCanBeStopped(bool useRealXps);

AlignTaskCanBeStopped(true);

Page 19: Clean code

© Sioux 2012 | Confidential | 19

Parameters example

Circle makeCircle(Point center, double radius);

public void AlignTaskCanBeStoppedWith RealXps();

public void AlignTaskCanBeStoppedWith SimulatedXps();

Page 20: Clean code

© Sioux 2012 | Confidential | 20

Don’t repeat yourself

Duplication DRY principle (Dave Thomas) Once, and only once (Kent Beck) Duplication is a missed opportunity for abstraction Examples:o Identical code (copy/paste)o Switch/case (or if/else) chainso Similar algorithms

Design patterns and OO itself are strategies for eliminating duplication

Page 21: Clean code

© Sioux 2012 | Confidential | 21

DRY example

public void scaleToOneDimension( float desiredDimension, float imageDimension) {

if (Math.abs(desiredDimension – imageDimension) < errorThreshold)

return;

float scalingFactor = desiredDimension / imageDimension;

scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);

RenderOp newImage = ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor);

image.dispose();

System.gc();

image = newImage;

}

public synchronized void rotate(int degrees) {

RenderOp newImage = ImageUtilities.getRotatedImage( image, degrees);

image.dispose();

System.gc();

image = newImage;

}

Page 22: Clean code

© Sioux 2012 | Confidential | 22

DRY example

public void scaleToOneDimension( float desiredDimension, float imageDimension) {

if (Math.abs(desiredDimension – imageDimension) < errorThreshold)

return;

float scalingFactor = desiredDimension / imageDimension;

scalingFactor = (float)(Math.floor(scalingFactor * 100) * 0.01f);

replaceImage(ImageUtilities.getScaledImage( image, scalingFactor, scalingFactor));

}

public synchronized void rotate(int degrees) {

replaceImage(ImageUtilities.getRotatedImage( image, degrees));

}

private void replaceImage(RenderedOp newImage) {

image.dispose();

System.gc();

image = newImage;

}

Page 23: Clean code

© Sioux 2012 | Confidential | 23

Magic numbers

Every number is a magic number

Replace it with a named constant

double circumference = radius * Math.PI * 2;

if (password.length() > 7)

public static final int MAX_PASSWORD_SIZE = 7;if (password.length() > MAX_PASSWORD_SIZE)

Page 24: Clean code

© Sioux 2012 | Confidential | 24

Applying clean code

Know how to write clean code Think about these small improvements

during:o New developmento Code reviewso Changing codeo Fixing bugs

So code will be easier to understand and cheaper to modify

Page 25: Clean code

© Sioux 2012 | Confidential | 25

Conclusion

Always code as if the guy who ends up maintaining your code will be a

violent psychopath who knows where you live.Code for readability.

Page 26: Clean code

© Sioux 2012 | Confidential | 26

Questions?

Page 27: Clean code

27© Sioux 2012 | Confidential |

[email protected]

+31 (0)40 2677100