Upload
lucinda-edwina-bates
View
218
Download
1
Embed Size (px)
Citation preview
CODE SMELLS
Steven [email protected]/in/SoftwareStew
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
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.
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.
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;}
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.
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" />
...
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).
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>
...
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.
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.
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
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
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.
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.
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
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
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.
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
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; }}
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.
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
Code Smells 23
Customer ExampleBetter
public class Customer2{ private DateTime generationTimestamp; private DateTime modificationTimestamp; private String recordId = "102"; /* ... */}
Clean Code, p. 22
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?”
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
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
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.
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
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.)
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
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)
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()
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
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
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.
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; }}
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.
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.
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.
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
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