Any fool can write code that a computer can understand - Good programmers write code that humans can understand
Martin Fowler
...and why
return new GeolocationResponseData(0, 0, 0, 0);is not a good API
eyalmrejen@gmail.comGreat code reads like great prose. It is succinct, expressive, and clear the first time you read it. It tries to be as linear as possible, guiding the reader through tough transitions with the knowledge that one wrong move could lose them entirely. Good code uses language and vocabulary with an understanding of its audience
Matthew Bischoff
https://matthewbischoff.com/code-is-prose/
This guy...
Martin Fowler (born 1963) is a British software developer, author, and international public speaker on software development, specializing in object-oriented analysis and design, UML, patterns, and agile software development methodologies, including extreme programming.
His 1999 book Refactoring popularised code refactoring. In 2018, a 2nd edition, this time in JavaScript, was published.
Refactoring is the process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure. It is a disciplined way to clean up code that minimizes the chances of introducing bugs. In essence when you refactor you are improving the design of the code after it has been written.
Martin Fowler
"Refactoring: Improving the Desing of Existing Code"
// Function to calculate total ticket cost
function printTicketStatement(tickets) {
let totalCost = 0;
for (const ticket of tickets) {
let thisCost = 0;
// Determine cost based on ticket type
switch (ticket.getType()) {
case Ticket.STANDARD:
thisCost += 10; // Base price for standard ticket
if (ticket.getQuantity() > 3) {
thisCost += (ticket.getQuantity() - 3) * 8;
}
break;
case Ticket.VIP:
thisCost += ticket.getQuantity() * 25;
break;
}
totalCost += thisCost;
}
return `Total cost is $${totalCost.toFixed(2)}`;
}// Function to calculate total ticket cost with discount
function printTicketStatement(tickets) {
let totalCost = 0;
let totalTickets = 0; // Accumulator for total quantity of tickets
// Step 1: Calculate base cost and total ticket count
for (const ticket of tickets) {
let thisCost = 0;
const quantity = ticket.getQuantity();
totalTickets += quantity; // Add to accumulator
switch (ticket.getType()) {
case Ticket.STANDARD:
thisCost += 10; // Base price
if (quantity > 3) {
thisCost += (quantity - 3) * 8;
}
break;
case Ticket.VIP:
thisCost += quantity * 25;
break;
case Ticket.PREMIUM: // New type
thisCost += quantity * 40; // $40 per Premium ticket
break;
}
totalCost += thisCost;
}
// Step 2: Apply discount based on total ticket count
let discountPercent = 0;
if (totalTickets >= 10) {
discountPercent = 20; // 20% off for 10+ tickets
} else if (totalTickets >= 5) {
discountPercent = 10; // 10% off for 5-9 tickets
} else if (totalTickets >= 3) {
discountPercent = 5; // 5% off for 3-4 tickets
}
const discountAmount = totalCost * (discountPercent / 100);
const finalCost = totalCost - discountAmount;
return `Total cost is ${finalCost.toFixed(2)} (Original: ${totalCost.toFixed(2)}, ${discountPercent}% discount applied)`;
}From this
To this
And then you want
printStatmentAsHtml()
https://gitlab..../cismanager/service/impl/UserPermissionServiceImpl.java
function MM_validateForm() { //v4.0
var
i,p,q,nm,test,num,min,max,errors='',args=MM_validateForm.arguments;
for (i=0; i<(args.length-2); i+=3) { test=args[i+2]; val=MM_findObj(args[i]);
if (val) { nm=val.name; if ((val=val.value)!="") {
if (test.indexOf('isEmail')!=-1) { p=val.indexOf('@');
if (p<1 || p==(val.length-1)) errors+='- '+nm+' must contain an e-mail address.\n';
} else if (test!='R') { num = parseFloat(val);
if (isNaN(val)) errors+='- '+nm+' must contain a number.\n';
if (test.indexOf('inRange') != -1) { p=test.indexOf(':');
min=test.substring(8,p); max=test.substring(p+1);
if (num<min || max<num) errors+='- '+nm+' must contain a number between '+min+' and '+max+'.\n';
} } } else if (test.charAt(0) == 'R') errors += '- '+nm+' is required.\n'; }
} if (errors) alert('The following error(s) occurred:\n'+errors);
document.MM_returnValue = (errors == '');These smells mean that if you need to change something in one place in your code, you have to make many changes in other places too. Program development becomes much more complicated and expensive as a result.
class Product {
private type: string;
constructor(type: string) {
this.type = type;
}
public getBasePrice() : number {
switch (this.type) {
case 'food':
return 10;
case 'drinks':
return 7;
case 'books':
return 3;
default:
return 0;
}
}
public getTaxPercent() : number {
switch (this.type) {
case 'food':
case 'drinks':
return 24;
case 'books':
return 8;
default:
return 0;
}
}
public getProductCategory(): string {
switch (this.type) {
case 'food':
case 'drinks':
return 'Food and Beverages';
case 'books':
return 'Education';
default:
return '-';
}
}
}
class Product {
private type: string;
constructor(type: string) {
this.type = type;
}
public getBasePrice() : number {
switch (this.type) {
case 'food':
return 10;
case 'drinks':
return 7;
case 'books':
return 3;
case 'vitamins':
return 1;
default:
return 0;
}
}
public getTaxPercent() : number {
switch (this.type) {
case 'food':
case 'drinks':
return 24;
case 'books':
return 8;
case 'vitamins':
return 3;
default:
return 0;
}
}
public getProductCategory(): string {
switch (this.type) {
case 'food':
case 'drinks':
return 'Food and Beverages';
case 'books':
return 'Education';
case 'vitamins':
return 'Pharmaceutical';
default:
return '-';
}
}
}
class Product {
private type: string;
static productTypes: { [key: string]: ProductInfo } = {
food: {
tax: 24,
basePrice: 10,
productCategory: 'Food and Beverages'
},
drinks: {
tax: 24,
basePrice: 7,
productCategory: 'Food and Beverages'
},
books: {
tax: 8,
basePrice: 3,
productCategory: 'Education'
},
}
constructor(type: string) {
this.type = type;
}
public getBasePrice() : number {
return Product.productTypes[this.type].basePrice || 0;
}
public getTaxPercent() : number {
return Product.productTypes[this.type].tax || 0;
}
public getProductCategory(): string {
return Product.productTypes[this.type].productCategory || '-';
}
}
https://avraam.dev/posts/divergent-change
A change leads to Shotgun surgery
public class Account {
private String type;
private String accountName;
private int amount;
public Account (String type, String accountName, int amount) {
this.type = type;
this.accountName = accountName;
this.amount = amount;
}
public void debit(int debit) throws Exception {
if(amount < 500) throw new Exception("Minimum balance should be over 500");
amount = amount - debit;
}
public void transfer(Account from, Account to, int creditAmount) throws Exception {
if(from.amount < 500) throw new Exception("Minimum balance should be over 500");
to.amount = amount - creditAmount;
}
public void sendWarningMessage() throws Exception {
if(amount < 500)
eventBroker.publishWarningMessage("Minimum balance should be over 500")
}
}Refactor Move To Method: isAccountUnderFlow()
Divergent Change resembles Shotgun Surgery but is actually the opposite smell. Divergent Change is when many changes are made to a single class. Shotgun Surgery refers to when a single change is made to multiple classes simultaneously.
//The class definition for Account
class Account {
private profit: number = 0;
//constructor
constructor() {
}
//set the profit member to a new value
setProfit(profit: number) {
this.profit = profit;
}
//return the profit from this account
getProfit() {
return this.profit;
}
}All comments here are worthless
//Check to see if the employee is eligible for full benefits
if(employee.flags & HOURLY_FLAG && (employee.age > 65)) {
//
}
if(employee.isEligibleForBenefits()) {
//
}
this.byteOPos = writeBytes(pngIdBytes, 0);
//hdrPos = bytePos;
writeHandler();
writeResolution();
//dataPos = bytePos;
if(writeImageData()) {
writeEnd();
this.pngBytes = resizeByArray(this.pngBytes, this.maxPos);
}A magic number is a numeric literal (8080, 200, 404, 5, 8) that is used in the middle of a block of code without explenation
double potentialEnergy(double mass, double height) {
return mass * height * 9.81;
} public static final double GRAVITATIONAL_CONSTANT = 9.81;
double potentialEnergy(double mass, double height) {
return mass * height * GRAVITATIONAL_CONSTANT;
} const sentences = nums.reduce((acc, curr) => {
switch (curr) {
case 96:
case 50:
case 37:
case 2:
acc.add('The page includes abnormal/broken DOM elements')
break
case 14:
case 31:
acc.add('The page contain abnormal/broken HTML attributes')
break
case 4:
acc.add('The page does not use the best practices for securing sensitive inputs')
break
case 34:
acc.add('The page utilizes suspicious page execution data')
break
case 108:
acc.add('The page contains unnecessary different origin data')
break
case 1:
acc.add('This page domain and URL are suspicious')
break
default:
break
}
return acc
}, new Set<string>())Magic Numers
Magic Strings....are the same issue
/**
* This method will validate the given device association details.
* In case the validation fails, an exception will be thrown with a relevant error message.
* @param workspace - the shop workspace
* @param deviceAssociationDto - shop device DAO
* @param action - indicates which action type is handled
* @param accountId - the id of the account
* @param customerRefIdsForAccount
* @throws ValidationException
*/
private List<ValidationExceptionMessages> validateAction(Long workspace, DeviceAssociationDetailsDto deviceAssociationDto, DeviceAssociationSaveAction action, String accountId, List<String> customerRefIdsForAccount) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT);
List<ValidationExceptionMessages> validationExceptionMessages = new ArrayList<>();
if (action != null && action.equals(DeviceAssociationSaveAction.RMA_ASSIGN_DEVICE)) {
return validationExceptionMessages;
}
if (StringUtils.isEmpty(deviceAssociationDto.getSerial())) {
validationExceptionMessages.add(ValidationExceptionMessages.SERIAL_IS_MANDATORY);
}
if(StringUtils.isNotEmpty(deviceAssociationDto.getSerial()) && !verifySerialExistInCustomer(accountId, deviceAssociationDto.getSerial(), customerRefIdsForAccount)){
validationExceptionMessages.add(ValidationExceptionMessages.INVALID_SERIAL);
}
// if (StringUtils.isEmpty(deviceAssociationDto.getShopCode()) && deviceAssociationDto.getSubRegionId()==null && deviceAssociationDto.getRegionId()==0) {
if (deviceAssociationDto.getEntityId() == 0) {
validationExceptionMessages.add(ValidationExceptionMessages.ILLEGAL_REGION); // At least region id should be filled
}
try {
if (StringUtils.isEmpty(deviceAssociationDto.getFrom())) {
validationExceptionMessages.add(ValidationExceptionMessages.FROM_DATE_MANDATORY);
} else if(!TimeUtils.isValidDateFormat(deviceAssociationDto.getFrom(), DATE_FORMAT)) {
validationExceptionMessages.add(ValidationExceptionMessages.INVALID_DATE_FORMAT);
} else if(action.equals(DeviceAssociationSaveAction.ADD_ASSIGNMENT_HISTORY)){
Date from = simpleDateFormat.parse(deviceAssociationDto.getFrom());
Date to = simpleDateFormat.parse(deviceAssociationDto.getTo());
if(!(from.before(new Date()) || from.equals(new Date()))) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_HISTORY_CAN_NOT_BE_IN_THE_FUTURE);
}
if (to.before(from)) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_HISTORY_TO_BEFORE_FROM);
}
}
// Partial mandatory fields validations, if mandatory field is missing --> no need to continue the validation
if (!validationExceptionMessages.isEmpty()) {
return validationExceptionMessages;
}
CustomerElementType elementType = CustomerElementType.valueOf(deviceAssociationDto.getAssignmentEntityType());
if(elementType.equals(CustomerElementType.REGION) && deviceAssociationDto.getEntityId()==0) {
validationExceptionMessages.add(ValidationExceptionMessages.ILLEGAL_REGION);
} else if(elementType.equals(CustomerElementType.SUB_REGION) && deviceAssociationDto.getEntityId()==0) {
// Long shopIdByCode = organizationStructureService.getShopIdByCode(deviceAssociationDto.getShopCode(), workspace);
// if (StringUtils.isNotEmpty(deviceAssociationDto.getShopCode()) && (shopIdByCode == null)) {
validationExceptionMessages.add(ValidationExceptionMessages.SHOP_CODE_NOT_EXIST);
// }
}
DeviceDto device = deviceService.getDeviceBySerial(deviceAssociationDto.getSerial());
if (device == null) {
validationExceptionMessages.add(ValidationExceptionMessages.DEVICE_ASSOCIATION_NOT_EXIST);
}
if(action.equals(DeviceAssociationSaveAction.ADD_ASSIGNMENT_HISTORY)){
if(StringUtils.isEmpty(deviceAssociationDto.getTo())) {
validationExceptionMessages.add(ValidationExceptionMessages.TO_DATE_MANDATORY);
} else if(!TimeUtils.isValidDateFormat(deviceAssociationDto.getTo(), DATE_FORMAT)) {
validationExceptionMessages.add(ValidationExceptionMessages.INVALID_DATE_FORMAT);
}else {
Date to = null;
if (StringUtils.isNotEmpty(deviceAssociationDto.getTo())) {
to = simpleDateFormat.parse(deviceAssociationDto.getTo());
}
if(!(to.before(new Date()) || to.equals(new Date()))) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_HISTORY_CAN_NOT_BE_IN_THE_FUTURE);
}
}
}
List<DeviceAssociation> deviceAssociationHistories = accountDeviceDao.getDeviceAssociationHistory(accountId, deviceAssociationDto.getSerial());
if (CollectionUtils.isNotEmpty(deviceAssociationHistories)) {
Date from = simpleDateFormat.parse(deviceAssociationDto.getFrom());
Date to = null;
if (StringUtils.isNotEmpty(deviceAssociationDto.getTo())) {
to = simpleDateFormat.parse(deviceAssociationDto.getTo());
}
for (DeviceAssociation deviceAssociationHistory : deviceAssociationHistories) {
// Avoid comparing range to itself
if(deviceAssociationHistory.getId() == deviceAssociationDto.getId()) {
continue;
}
// Verify there is no conflict with current shop assignment
if(deviceAssociationHistory.getToDate()==null && to!=null) {
// [from]=currentAssignmentFrom
if (from.equals(deviceAssociationHistory.getFromDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
// [to]>=currentAssignmentFrom
if (to.equals(deviceAssociationHistory.getFromDate()) || to.after(deviceAssociationHistory.getFromDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
}
// [from]=rangeFrom or [from]=rangeTo
if (from.equals(deviceAssociationHistory.getFromDate()) || from.equals(deviceAssociationHistory.getToDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
// Verify there is no conflict with history shop assignment
if (deviceAssociationHistory.getToDate() != null && to != null) {
// [to]=rangeFrom or [to]=rangeTo
if (to.equals(deviceAssociationHistory.getFromDate()) || to.equals(deviceAssociationHistory.getToDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
// rangeFrom < [from] < rangeTo
if (from.after(deviceAssociationHistory.getFromDate()) && from.before(deviceAssociationHistory.getToDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
// rangeFrom < [to] < rangeTo
if (to.after(deviceAssociationHistory.getFromDate()) && to.before(deviceAssociationHistory.getToDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
// [from] < rangeFrom and rangeTo < [to]
if (from.before(deviceAssociationHistory.getFromDate()) && to.after(deviceAssociationHistory.getToDate())) {
validationExceptionMessages.add(ValidationExceptionMessages.ASSIGNMENT_ALREADY_EXIST_FOR_DEVICE);
}
}
}
}
} catch (ParseException e) {
e.printStackTrace();
}
return validationExceptionMessages;
}user.setDisabled(true);
user.setDisabled(false);
//
settings.createSetting("userAge", true);
settings.createSetting("userAge", false);
//
//https://angular.io/api/forms/ControlValueAccessor
form.setDisabledState(true);
form.setDisabledState(false);
user.enable();
user.disable();
//Chrome DevTools source
createLocalSetting: function(name){
this.createSetting(name, true)
}
settings.createLocalSetting("userAge");
if((now() > months[3]) && (now() < months[10])) {
//
}
if(isWinter(now())) {
//
}const monthOf => index => months[index];
const March = 3;
const October = 10;
if((now() > monthOf(March)) && (now() < monthOf(October))) {
//
}
req.body && Object.keys(req.body).length > 0 && req.body.keyId && req.body.iv && req.body.payloadif(isRequestBodyEncrypted()) {
//
}Code Smell - Code Duplication
Still counts - even though it's not exactly duplication
Failure of the Single Responsibility Principle
public class Employee
{
public int ID { get; set; }
public DateTime BirthDate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string SpouseFirstName { get; set; }
public string SpouseLastName { get; set; }
public string ChildOneFirstName { get; set; }
public string ChildOneLastName { get; set; }
...
}
if(sentence.toLowerCase().lastIndexOf(badWord) === sentence.length - badWord.length) {
return ThreatLevel.MEDIUM;
} if(badWordIsAtTheEnd(badWord, sentence)) {
return ThreatLevel.MEDIUM;
}class Employee {...}
class Salesman extends Employee {
get name() {...}
}
class Engineer extends Employee {
get name() {...}
}class Employee {
get name() {...}
}
class Salesman extends Employee {...}
class Engineer extends Employee {...}function amountInvoiced(startDate, endDate, priority) {...} //1st time
function amountReceived(startDate, endDate, priority) {...} //again
function amountOverdue(startDate, endDate, priority) {...} // and again
//define "factorish" function
const aDateRageWithPriority = (startDate, endDate, priority) => ({startDate, endDate, priority});
//invoke
const range = aDateRageWithPriority(startDate, endDate, priority);
function amountInvoiced(range) {...}
function amountReceived(range) {...}
function amountOverdue(range) {...}
//PLEASE NOTE THAT THIS FUNCTION DOES 2 THINGS...
function setDimension(name, value) {
if (name === "height") {
this._height = value;
return;
}
if (name === "width") {
this._width = value;
return;
}
}
function setHeight(value) {this._height = value;}
function setWidth (value) {this._width = value;}
Replace Parameter with Explicit Methods
//code has intimate knowledge of the person data structure
const manager = aPerson.department.manager;
//or
const {department: {manager}} = aPerson;
//extract (selector) function
const selectManagerFrom = aPerson => aPerson.department.manager
//now use it cleanly
const manager = selectManagerFrom(aPerson);
//Also please look at the style of english, how more fluent it is,
//than rather coding - no getManager() for example
//Code should be written like prose
The sloppy way - doing it manually
https://www.youtube.com/watch?v=W8LLUIIwSBwThe Copy & Paste way
https://www.youtube.com/watch?v=3GK2JckFX5gThe Best way - use the IDE
https://www.youtube.com/watch?v=z_C-CBoTtx0