41
CODE SMELLS Steven Stewart [email protected] www.LinkedIn.com/in/SoftwareStew

CODE SMELLS Steven Stewart [email protected]

Embed Size (px)

Citation preview

Page 1: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

CODE SMELLS

Steven [email protected]/in/SoftwareStew

Page 2: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 2

Recommended Reading• Clean Code: A Handbook of Agile Software Craftsmanship

by Robert C. Martin

• Refactoring: Improving the Design of Existing Codeby Martin Fowler

• Code Complete: A Practical Handbook of Software Construction (2nd Edition)

by Steve McConnell

• Head First Design Patternsby Eric Freeman & Elisabeth Freeman

• Working Effectively with Legacy Codeby Michael C. Feathers

Page 3: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 3

Code Smell• A symptom of your code that may indicate a more serious problem.

• Structure in the design that negatively impacts design principles and quality. (Fowler)

• Code that is poorly structured, inefficient, or hard to maintain.

• They are usually not bugs, since the code as written may work correctly.

Page 4: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 4

Code Smell• Recognizing certain code smells can lead you to specific refactorings that can improve your code.

• However, determining what is a code smell is subjective. Varies by:• language,• development methodology• developer

• Determining how to and whether to correct a code smell is similarly subjective.

Page 5: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 5

SystemAccess ExampleWhat’s wrong with this code?public List<AccessRequestService.SiteAccessRequest> GetAccessRequestForReApproval(List<string> lstChannelTitle, List<string> lstApproversTitle, int primaryNotificationTime, int timeCycle, DateTime startDate, string approverGuid, List<long> siteHierarchyAppKeys, bool reApprovalPilot){ AccessRequestService.AccessRequestServiceClient accessRequestServiceClient = null; List<StaffingBusinessCore.AccessRequestService.SiteAccessRequest> lstSiteAccessRequest = null;

try { accessRequestServiceClient = new AccessRequestService.AccessRequestServiceClient();

if (ConfigSettingsHelper.ESI_Authentication.RequireAuthentication) { accessRequestServiceClient.ClientCredentials.Windows.ClientCredential.UserName = ConfigSettingsHelper.ESI_Authentication.UserName; accessRequestServiceClient.ClientCredentials.Windows.ClientCredential.Password = ConfigSettingsHelper.ESI_Authentication.Password; }

lstSiteAccessRequest = accessRequestServiceClient.GetAccessRequestForReApproval(lstChannelTitle, lstApproversTitle, primaryNotificationTime, timeCycle, startDate, approverGuid, siteHierarchyAppKeys, reApprovalPilot); } catch (Exception ex) { base.ManageException(ex.Message, ex); throw; } finally { if (accessRequestServiceClient != null && accessRequestServiceClient.State != System.ServiceModel.CommunicationState.Closed) { accessRequestServiceClient.Close(); } }

return lstSiteAccessRequest;}

Page 6: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 6

SystemAccess Example• If you can’t read the code easily, there might be errors lurking.• Format code (indentation) so it can be seen on a normal

screen.• Parameters in method run off right side of screen.

• Line them up.• Arguments in method call run off right side of screen.

• Line them up.• The if statement has two parameters on the same line.

• Line them up.

• Having too many parameters in a method is a code smell.• Method doing too much?• Create a class that bundles the parameters, and pass that

around.

Page 7: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 7

LoginPage.html ExampleWhat’s wrong with this code?

...

<input class="prologinBut" langtag="login" style="color: #FFFFFF; font-family: Verdana;" id="SubmitCreds" value="LOG IN" onclick="proclkLgn();" type="submit" name="SubmitCreds" />

...

Page 8: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 8

LoginPage.html Example• Don’t allow HTML tag contents or text to scroll off right side of screen.• You might be concerned about formatting so that lots more

white space is added to the file, causing slightly longer download times, but at least make it readable.

• Even text between tags can have line breaks for code formatting without affecting the runtime appearance.

• Another smell -- in this code, the use of the table to format text violates the separation of content (HTML) from formatting (CSS).

Page 9: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 9

MainPage.xaml ExampleWhat’s wrong with this code?

...

<toolkit:DockPanel x:Name="ButtonsDockPanel" LastChildFill="True"> <sdk:Label x:Name="LogoLabel" Content="Lottery Wheel" VerticalAlignment="Center" HorizontalContentAlignment="Center" Margin="12,0" FontSize="24" IsTabStop="True" TabIndex="5"></sdk:Label> <TextBox x:Name="LowestLotteryNumberTextBox" Text="{Binding LowestLotteryNumber}" TextWrapping="NoWrap" Height="23" Width="66" Margin="4,0" Tag="Lowest #" ToolTipService.ToolTip="Enter lowest lottery number" KeyDown="LotteryNumberTextBox_KeyDown" GotFocus="LotteryNumberTextBox_GotFocus" LostFocus="LotteryNumberTextBox_LostFocus" Loaded="LotteryNumberTextBox_Loaded" TextChanged="LotteryNumberTextBox_TextChanged" TabIndex="10" /> <TextBox x:Name="HighestLotteryNumberTextBox" Text="{Binding HighestLotteryNumber}" TextWrapping="NoWrap" Height="23" Width="66" Margin="4,0" Tag="Highest #" ToolTipService.ToolTip="Enter highest lottery number" KeyDown="LotteryNumberTextBox_KeyDown" GotFocus="LotteryNumberTextBox_GotFocus" LostFocus="LotteryNumberTextBox_LostFocus" Loaded="LotteryNumberTextBox_Loaded" TextChanged="LotteryNumberTextBox_TextChanged" TabIndex="20" /> <Button x:Name="CreateWheelButton" Content="Create" Command="{Binding SetupWinnerSelectorCommand}" CommandParameter="" Height="23" Width="75" Margin="4,0" TabIndex="30" /> <Button x:Name="ResizeWheelButton" Content="Resize" Command="{Binding ResizeWinnerSelectorCommand}" CommandParameter="" Height="23" Width="75" Margin="4,0" TabIndex="40" /> <Button x:Name="CloseButton" Content="Close" Command="{Binding CloseCommand}" CommandParameter="" Height="23" Width="75" Margin="4,0" toolkit:DockPanel.Dock="Right" TabIndex="70" /> <Button x:Name="OptionsButton" Content="Options" Command="{Binding GetOptionsCommand}" CommandParameter="" Height="23" Width="75" Margin="4,0" toolkit:DockPanel.Dock="Right" TabIndex="60" /> <Button x:Name="SpinWheelButton" Content="Spin" Command="{Binding SelectWinnerCommand}" CommandParameter="" Height="23" Margin="4,0" toolkit:DockPanel.Dock="Right" TabIndex="50" /></toolkit:DockPanel>

...

Page 10: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 10

MainPage.xaml Example• In XAML, unlike HTML, there’s no reason not to just line up the attributes to make the XML more readable.• In VS, use Ctrl-K, Ctrl-D to auto-format; set your preferred

formatting options.

• Consistently list the most important attributes first (e.g., Name, ID).

• This code also has formatting code (e.g., height and width attributes) in the XAML.• Consider putting this in a style, or allow components to auto-

size.

• Repeated Height attribute violates the DRY principle.

Page 11: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 11

Thank you, Bob Martin!

The following examples are borrowed, with a few modifications of my own, from Bob Martin’s book, Clean Code.

Page 12: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 12

ListProcessor1 ExampleWhat’s wrong with this code?

public List<int[]> GetThem(){ List<int[]> list1 = new List<int[]>(); foreach (var x in TheList) { if (x[0] == 4) { list1.Add(x); } }

return list1;}

Clean Code, p. 18

Page 13: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 13

ListProcessor1 Example• Why is it so hard to understand what this code is doing?• No complex expressions.• Well formatted.• Only three variables and two constants• Just a list of arrays

Page 14: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 14

ListProcessor1 Example• The code requires that we know answers to questions like:• What kinds of things are in TheList?• What is the significance of the zeroth subscript of an item

in TheList?• What is the significance of the value 4?• How would I use the list being returned?

• The answers to these questions are not present in the code sample, but they could have been.

Page 15: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 15

ListProcessor2 Example• Suppose we’re working on a mine sweeper game.

• We can rename TheList to GameBoard.

• Each cell on the board is represented by a simple array.

• The zeroth item in a cell’s array is the status value.

• A status value of 4 means the cell is “flagged”.

• Now we’re using the domain’s ubiquitous language.

Page 16: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 16

ListProcessor2 ExampleBetter

public List<int[]> GetFlaggedCells(){ List<int[]> flaggedCells = new List<int[]>(); foreach (int[] cell in GameBoard) { if (cell[STATUS_INDEX] == FLAGGED) { flaggedCells.Add(cell); } }

return flaggedCells;}

Clean Code, p. 19

Page 17: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 17

ListProcessor2 ExampleBetter … but what’s wrong with this code?

public List<int[]> GetFlaggedCells(){ List<int[]> flaggedCells = new List<int[]>(); foreach (int[] cell in GameBoard) { if (cell[STATUS_INDEX] == FLAGGED) { flaggedCells.Add(cell); } }

return flaggedCells;}

Clean Code, p. 19

Page 18: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 18

ListProcessor3 Example• Code smell here is the if statement. Beware if statements that are difficult to decipher or that expose implementation details.

• In this case we can write a simple class for a cell instead of using an array of ints.

• The Cell class can expose an intention-revealing function called IsFlagged.

• Now the details of the Cell’s implementation (e.g., an array of ints) is encapsulated within the class.

Page 19: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 19

ListProcessor3 Example

public List<Cell> GetFlaggedCells(){ List<Cell> flaggedCells = new List<Cell>(); foreach (Cell cell in GameBoard) { if (cell.IsFlagged()) { flaggedCells.Add(cell); } }

return flaggedCells;}

Clean Code, p. 19

Page 20: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 20

Cell ExampleWhat’s wrong with this code?

public class Cell{ private int[] cellProperties;

readonly int STATUS_INDEX = 0; readonly int FLAGGED = 4;

public bool IsFlagged() { return cellProperties[STATUS_INDEX] == FLAGGED; }}

Page 21: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 21

ListProcessor3 Example• The implementation of the Cell class still reflects the original implementation using the array of ints.

• This implementation uses “magic numbers” (e.g., 4 means flagged). The use of magic numbers or magic strings is a code smell.• It’s not strongly typed.• Possibly refactor using enumerated list.

• The use of an array of ints to store non-numeric data is also suspicious. Very old technique – before OO.• Possibly refactor using class properties instead.

Page 22: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 22

Customer ExampleWhat’s wrong with this code?

public class Customer1{ private DateTime genymdhms; private DateTime modymdhms; private String pszqint = "102"; /* ... */}

Clean Code, p. 22

Page 23: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 23

Customer ExampleBetter

public class Customer2{ private DateTime generationTimestamp; private DateTime modificationTimestamp; private String recordId = "102"; /* ... */}

Clean Code, p. 22

Page 24: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 24

Customer Example• Use meaningful names.

• Class names are nouns or noun phrases.• Method names are verbs or verb phrases.

• Avoid abbreviations, especially ones that can be misinterpreted or are not common in the domain.

• Use pronounceable names.• In the example, genymdhms means “generation date,

year, month day, hour, minute, and second.”• But with revised code you can have an intelligent

conversation: “Hey Mikey, take a look at record ID 102. The generation timestamp is set to tomorrow’s date! How can that be?”

Page 25: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 25

Task Loop ExampleWhat’s wrong with this code?

for (int j = 0; j < 34; j++){ s += (t[j] * 4) / 5;}

Clean Code, p. 23

Page 26: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 26

Task Loop ExampleBetter:

for (int j = 0; j < NUMBER_OF_TASKS; j++){ int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks;}

Clean Code, p. 23

Page 27: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 27

Task Loop Example• Single-letter names and numeric constants are not easily searchable.

• Only use single-letter names sparingly as local variables inside short methods.

• Consider how much easier it will be to find WORK_DAYS_PER_WEEK than to find all the places where 5 exists and filter the list down to just the instances with the intended meaning.

Page 28: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 28

UserValidation ExampleWhat’s wrong with this code?

public bool CheckPassword(string username, string password){ User user = UserGateway.FindByName(username); if (user != null) { string codedPhrase = user.GetPhraseEncodedByPassword(); string phrase = Cryptographer.Decrypt(codedPhrase, password);

if ("Valid Password".Equals(phrase)) { Session.Initialize(); return true; } } return false;}

Clean Code, p. 44

Page 29: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 29

UserValidation Example• Methods should not have side effects.

• The call to Session.Initialize() is a code smell – or worse!

• The name of this method does not imply that it will initialize the session. This could have a bug-producing impact if the user of this method is not aware of this.

• If you must connect two effects, name the method appropriately, e.g., CheckPasswordAndInitializeSession(). (But this name itself would be a code smell, implying that the method violates the “do one thing” rule.)

Page 30: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 30

Employee ExampleWhat’s wrong with this code?

public Decimal CalculatePay(Employee1 employee){ switch (employee.Classification) { case EmployeeClassification.Commissioned: return calculateCommissionedPay(employee); case EmployeeClassification.Hourly: return calculateHourlyPay(employee); case EmployeeClassification.Salaried: return calculateSalariedPay(employee); default: throw new InvalidEmployeeClassificationException(); }}

Clean Code, p. 38

Page 31: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 31

Employee Example• A switch statement is a code smell.

• They tend to be long. (Methods should be short.) It will grow even longer when new employee classifications are added.

• It does more than one thing.• It violates the Single Responsibility Principle, because

there is more than one reason for it to change.• It violates the Open Closed Principle, because it must

change whenever new types are added.

• Worst of all, there will probably be other methods that have the same poor structure.• IsPayday(Employee employee, DateTime date)• DeliverPay(Employee employee, Decimal pay)

Page 32: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 32

Employee Example• The solution is to bury the switch statement in a factory, and never let anyone else see it. That’s the only place you’ll need the switch statement.

• The factory will use the switch statement to create instances of the derivatives of Employee.

• Methods for a specific Classification will be defined in the derived Employee class associated with that Classification.• CalculatePay()• IsPayday()• DeliverPay()

Page 33: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 33

Employee Examplepublic class EmployeeFactory: IEmployeeFactory{ public Employee MakeEmployee(EmployeeRecord employeeRecord) { switch (employeeRecord.Classification) { case EmployeeClassification.Commissioned: return new CommissionedEmployee(employeeRecord); case EmployeeClassification.Hourly: return new HourlyEmployee(employeeRecord); case EmployeeClassification.Salaried: return new SalariedEmployee(employeeRecord); default: throw new InvalidEmployeeClassificationException( employeeRecord.Classification); } } }}Clean Code, p. 39

Page 34: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 34

Employee ExampleThe old Payroll class is no longer needed. Just call the CalculatePay method of the Employee. The appropriate specific implementation will automatically be used.public abstract class Employee{ public abstract Decimal CalculatePay(); public abstract bool IsPayday(); public abstract void DeliverPay(Decimal pay);}

Clean Code, p. 39

Page 35: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 35

Thank you, Head First!

The following example is borrowed, with a few modifications of my own, from Head First Design Patterns, Chapter 10.

Page 36: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 36

Gumball Machine ExampleWhat’s wrong with this code?

public void InsertQuarter(){ switch (state) { case GumballMachineState.SoldOut: Console.WriteLine("The machine is sold out."); break; case GumballMachineState.NoQuarter: state = GumballMachineState.HasQuarter; Console.WriteLine("You inserted a quarter."); break; case GumballMachineState.HasQuarter: Console.WriteLine("You can't insert another quarter."); break; case GumballMachineState.Sold: Console.WriteLine(“We're already giving you a gumball."); break; }}

Page 37: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 37

Gumball Machine Example• Same problems with this switch statement as before.

• Especially, the code isn’t adhering to the Open Closed principle.• If you change the requirements (e.g., “1 in 10 gets a free

gumball”), a new state has to be added, which will require code changes everywhere. (Not just code extensions, but code modifications.)

• The methods are too long and will get longer.

• The state transitions are buried in the middle of case statements.

Page 38: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 38

Gumball Machine Example• The State Pattern can help clean up this code.

• The State Pattern allows an object to alter it’s behavior when its internal state changes. The object appears to change its class.

• You create a concrete state class for each possible state, all descending from IState.

Page 39: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 39

Gumball Machine Example• The code in the case clauses of all those switch statements is distributed among the event handlers of the all the concrete State classes.

• Now instead of needing a switch statement for every event, the GumballMachine class only needs to call the event of the currently active State class, and let it do the work.

• All the event handler methods are now quite short, and there are no more switch statements.

Page 40: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 40

Gumball Machine ExampleExcerpt from new GumballMachine class:

public void InsertQuarter(){ state.InsertQuarter();}

public void EjectQuarter(){ state.EjectQuarter();}

public void TurnCrank(){ state.TurnCrank(); state.Dispense();}

...

Head First Design Patterns, Chapter 10

Page 41: CODE SMELLS Steven Stewart Steve@SoftwareStew.com

Code Smells 41

Gumball Machine ExampleExcerpt from new HasQuarterState class:

public void InsertQuarter(){ Console.WriteLine("You can't insert another quarter.");}

public void EjectQuarter(){ Console.WriteLine("Quarter returned."); gumballMachine.SetState(gumballMachine.NoQuarterState);}

public void TurnCrank(){ Console.WriteLine("You turned..."); gumballMachine.SetState(gumballMachine.SoldState);}...

Head First Design Patterns, Chapter 10