Transcript

Clean code

Roy Nitert

18-6-2013

GEWIS lunchlezing

© 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

© 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.

© Sioux 2012 | Confidential | 4

Requirements

© 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

© Sioux 2012 | Confidential | 6

Recognizing 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

© 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

© 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)

© Sioux 2012 | Confidential | 10

Comments

© 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();

© 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

© 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;}

© 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

© 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;

}

© 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;

}

© 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

© Sioux 2012 | Confidential | 18

Parameters example

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

public void AlignTaskCanBeStopped(bool useRealXps);

AlignTaskCanBeStopped(true);

© Sioux 2012 | Confidential | 19

Parameters example

Circle makeCircle(Point center, double radius);

public void AlignTaskCanBeStoppedWith RealXps();

public void AlignTaskCanBeStoppedWith SimulatedXps();

© 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

© 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;

}

© 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;

}

© 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)

© 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

© 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.

© Sioux 2012 | Confidential | 26

Questions?

27© Sioux 2012 | Confidential |

roy.nitert@sioux.eu

+31 (0)40 2677100

top related