clean code development
DESCRIPTION
Good and Bad CodeThe Broken Window TheoryThe Grand Redesign in the SkyThe Sushi Chef RuleThe Hotel Room RuleThe Boy Scout RuleOOP Patterns and PrinciplesSOLID PrinciplesHow to measure clean code?ToolsTRANSCRIPT
Clean Code Development
Peter Gfader#netug
Delivering Awesome Web Applications
C# and .NET (Java not anymore)
TestingAutomated tests
Agile, ScrumScrum Developer Trainer
Technology aficionado SilverlightASP.NETWindows FormsLINQ,...
Peter Gfaderhttp://blog.gfader.com/
twitter.com/peitor#netug
• Why code matters• Good and Bad Code• The Broken Window Theory• The Grand Redesign in the Sky• The Boy Scout Rule
• OOP Patterns and Principles• SOLID Principles
• How to measure clean code?• Tools
Agenda
1903 Wright brothers flew 59 seconds
Why code matters?
http://www.reddit.com/r/AskReddit/comments/dlrjs/whats_the_most_mindblowing_fact_you_heardread_in/
1969We landed on the moon
Why code matters?
TodayWe fly around the world in 32 hoursTourists in space
Why code matters?
1903 - 0 computers
Why code matters?
40 years later - a handful of computers
Why code matters?
Today > 500 billion programmable devices
Why code matters?
Today > 500 billion programmable devices (more than humans on earth)
Why code matters?
2100Programmable devices everywhereUnder skin, in brain, in blood… like dust…
Why code matters?
Who programs those?
Why code matters?
Why code matters?
What tools do we use?
Why code matters?
Can we trust our code?
Important?
What is good code?
It's gotta ship?
It's gotta pass the tester?
It's gotta implement requirements?
It's gotta be reasonably performant?
"Wartung"? (aka Maintainability)
What is good code?
What is bad code?
What is bad code?
What is bad code?
while ((!found) && (pos < (fileContent.Length - 6))){ byteData = new byte[6]; Array.Copy(fileContent, pos, byteData, 0, 6); pos = pos + 6; str_byteData = enc.GetString(byteData); if (str_byteData.Contains("s")) { posE_byteData = str_byteData.IndexOf("s"); pos = pos + (posE_byteData - 6); Array.Copy(fileContent, pos, byteData, 0, 6); pos = pos + 6; if (byteData[0] == 0x73) // 's' { if (byteData[1] == 0x74) // 't' { if (byteData[2] == 0x72) // 'r' { if (byteData[3] == 0x65) // 'e' { if (byteData[4] == 0x61) // 'a' { if (byteData[5] == 0x6D) // 'm' { found = true; break; } else { if (byteData[5] == 0x73) { pos = pos - 1; } } }
What is bad code?
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;}
What is bad code?
What is bad code?
• Hard to understand at first sight• Unmaintained• Messy• No one cares
What is bad code?
• Hard to understand at first sight• Unmaintained• Messy• No one cares
"Wartung"? (aka Maintainabilty)
What is bad code?
Bad Code
Is SCARY!!!
Why are we writing bad code?
Broken Window Theory
Broken Window Theory
The rewrite
Netscape
rewrote Netscape 4.0 and released it after three years as Netscape 6.0
Borland
rewrote dBase and Quattro Pro
Microsoft
Rewrote Vista~60%
What can we do?
Sushi chef rule
Clean up as you doNA
Hotel room rule
Let someone clean up every dayNA
The Boy Scout Rule
Leave the campground cleaner than you found it OK
How can we improve?
"Everything I have to change, in order to make the product owner happy!"
• Config files .config, .svc• XAML .xaml, .CSS, .. • Code .cs, .vb, .js, ..• Deployment scripts .ps• Batch files .bat
What is code?
OOP Principles
Solid PrinciplesS.o.l.i.d. Principles
Single Responsibility Principle
Only one reason to change
Robustness
Focus
Every entity should have a single responsibility
public class PrintServer { public string CreateJob(PrintJob data) { //... } public int GetStatus(string jobId) { //... } public void Print(string jobId, int startPage, int endPage) { //... }
public List<Printer> GetPrinterList() { //... } public bool AddPrinter(Printer printer) { //... }
public event EventHandler<JobEvent> PrintPreviewPageComputed;
public event EventHandler PrintPreviewReady;
// ...}
public class PrintServer {
public string CreateJob(PrintJob data) { //... }
public int GetStatus(string jobId) { //... }
public void Print(string jobId, int startPage, int endPage) { //... }
}
public class PrinterList {
public List<Printer> GetPrinterList() { //... }
public bool AddPrinter(Printer printer) { //... }}
OpenClose Principle
Open for extension
Close for modification
Every entity should be open for extension, but closed for modification
public void SaveToolbarStateSwitch() {
long version = Core.GetVisualStudioVersion();
Configuration.VSToolbarHeight = AddinCommandBar.Height.ToString(); Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString(); switch (version) { case 2003: case 2004: case 2005: Configuration.VSToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VSToolbarPosition = ((int)AddinCommandBar.Position).ToString(); Configuration.VS2010ToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString(); break; case 2008: Configuration.VSToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VSToolbarPosition = ((int)AddinCommandBar.Position).ToString(); break; case 2010: Configuration.VSToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VSToolbarLeft = AddinCommandBar.Left.ToString(); Configuration.VSToolbarTop = AddinCommandBar.Top.ToString(); Configuration.VSToolbarWidth = AddinCommandBar.Width.ToString(); break; default: Configuration.VS2010ToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VS2010ToolbarPosition = AddinCommandBar.Position.ToString(); break; }
public class ToolbarManager { public void SaveToolbarState() { var version = Core.GetVisualStudioVersion();
Configuration.VSToolbarHeight = AddinCommandBar.Height.ToString(); if (version <= 2003) { Configuration.VS2010ToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VS2010ToolbarPosition = AddinCommandBar.Position.ToString(); } else if (version >= 2005 && version <= 2008) { Configuration.VSToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VSToolbarPosition = ((int)AddinCommandBar.Position).ToString();
} else if (version == 2010) { Configuration.VSToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VSToolbarLeft = AddinCommandBar.Left.ToString(); Configuration.VSToolbarTop = AddinCommandBar.Top.ToString(); Configuration.VSToolbarWidth = AddinCommandBar.Width.ToString(); } else { Configuration.VSToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VSToolbarPosition = ((int)AddinCommandBar.Position).ToString(); Configuration.VS2010ToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString(); } }
private void SaveForVs2003() { Configuration.VSToolbarVisible = "False"; Configuration.VSToolbarPosition = ((int)AddinCommandBar.Position).ToString(); Configuration.VS2010ToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString(); }
private void SaveForVs2005() { Configuration.VSToolbarVisible = "True"; Configuration.VSToolbarPosition = ((int)AddinCommandBar.Position).ToString(); Configuration.VS2010ToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString(); Configuration.VS2010ToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; }
private void SaveForVs2008() { Configuration.VSToolbarPosition = "False"; Configuration.VS2010ToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VS2010ToolbarVisible = "False"; Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString();
} private void SaveForVs2010() { Configuration.VSToolbarVisible = AddinCommandBar.Visible ? "True" : "False"; Configuration.VSToolbarPosition = "False"; Configuration.VS2010ToolbarRowIndex = AddinCommandBar.RowIndex.ToString(); Configuration.VS2010ToolbarWidth = AddinCommandBar.Width.ToString(); Configuration.VS2010ToolbarHeight = AddinCommandBar.Height.ToString(); }
public class ToolbarManager { private readonly Dictionary<long, Action> _versionAction;
public ToolbarManager() { _versionAction = new Dictionary<long, Action>(); _versionAction.Add(2003, SaveForVs2003); _versionAction.Add(2005, SaveForVs2005); _versionAction.Add(2008, SaveForVs2008); _versionAction.Add(2010, SaveForVs2010); }
public void SaveToolbarStateBetter() { var version = Core.GetVisualStudioVersion();
if (_versionAction.ContainsKey(version)) { _versionAction[version].Invoke(); }
}
public class ToolbarManager { private readonly Dictionary<long, Action> _versionAction;
public ToolbarManager() { _versionAction = new Dictionary<long, Action>(); _versionAction.Add(2003, SaveForVs2003); _versionAction.Add(2005, SaveForVs2005); _versionAction.Add(2008, SaveForVs2008); _versionAction.Add(2010, SaveForVs2010); _versionAction.Add(2012, SaveForVs2012); }
private void SaveForVs2012() { Configuration.EnableSpeechRecognition = "True"; Configuration.HandGestureRecognition = AddinCommandBar.Top; }
Liskov Substitution Principle
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T
Liskov Substitution Principle
Subtypes must be substitutable for their base types
Inheritance and polymorphism
public class Rectangle { public int Width { get; set; }
public int Height { get; set; }
public int GetArea() { return Width*Height; } }
[TestFixture]public class RectangleTests{ [Test] public void CheckArea_PassingTest() { Rectangle r = new Rectangle(); CheckAreaOfRectangle(r); }
private void CheckAreaOfRectangle(Rectangle r) { r.Width = 5; r.Height = 2;
Assert.AreEqual(10, r.GetArea()); }
}
We need a Square!
public class Rectangle { protected int _width; public virtual int Width { get { return _width; } set { _width = value; } }
protected int _height; public virtual int Height { get { return _height; } set { _height = value; } }
public int GetArea() { return Width*Height; } }
public class Square : Rectangle { public override int Width { get { return _width; } set { _width = value; _height = value; } } public override int Height { get { return _height; } set { _height = value; _width = value; } } }
[TestFixture]public class RectangleTests{ [Test] public void CheckArea_PassingTest() { Rectangle r = new Rectangle(); CheckAreaOfRectangle(r); }
private void CheckAreaOfRectangle(Rectangle r) { r.Width = 5; r.Height = 2;
Assert.AreEqual(10, r.GetArea()); }
[Test] public void CheckArea_FAILINGTest() { Rectangle r = new Square(); CheckAreaOfRectangle(r); }
}
public class Rectangle { public int Width { get; set; }
public int Height { get; set; }
public int GetArea() { return Width * Height; } }
public class Square { public int Side { get; set; }
public int GetArea() { return Side * Side; }
}
Interface Segregation Principle
Don’t be force to implement unused methods
Avoid “Fat Interfaces”
Clients should not be forced to depend on methods they do not use
public override bool ValidateUser(string usercode, string password) { var returnValue = false;
MoneyService moneyServices = new MoneyService();
if (moneyServices.IsValid(usercode, password)) { returnValue = true; }
return returnValue; }
-- snip snip snip ----
public class MoneyMembershipProvider : MembershipProvider {
namespace System.Web.Security{ public abstract class MembershipProvider : ProviderBase { public abstract bool EnablePasswordRetrieval { get; } public abstract bool EnablePasswordReset { get; } public abstract bool RequiresQuestionAndAnswer { get; } public abstract string ApplicationName { get; set; } public abstract int MaxInvalidPasswordAttempts { get; } public abstract int PasswordAttemptWindow { get; } public abstract bool RequiresUniqueEmail { get; } public abstract MembershipPasswordFormat PasswordFormat { get; } public abstract int MinRequiredPasswordLength { get; } public abstract int MinRequiredNonAlphanumericCharacters { get; } public abstract string PasswordStrengthRegularExpression { get; }
public abstract MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status);
public abstract bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer);
public abstract string GetPassword(string username, string answer); public abstract bool ChangePassword(string username, string oldPassword, string newPassword); public abstract string ResetPassword(string username, string answer); public abstract void UpdateUser(MembershipUser user); public abstract bool ValidateUser(string username, string password); public abstract bool UnlockUser(string userName); public abstract MembershipUser GetUser(object providerUserKey, bool userIsOnline); public abstract MembershipUser GetUser(string username, bool userIsOnline); public abstract string GetUserNameByEmail(string email); public abstract bool DeleteUser(string username, bool deleteAllRelatedData); public abstract MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords); public abstract int GetNumberOfUsersOnline();
public abstract MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords);
public abstract MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords);
protected virtual byte[] EncryptPassword(byte[] password);
protected virtual byte[] EncryptPassword(byte[] password, MembershipPasswordCompatibilityMode legacyPasswordCompatibilityMode);
protected virtual byte[] DecryptPassword(byte[] encodedPassword); protected virtual void OnValidatingPassword(ValidatePasswordEventArgs e); public event MembershipValidatePasswordEventHandler ValidatingPassword; }}
public class AuctionsPlusMembershipProvider : MembershipProvider {
-- snip snip snip ----
public override bool ChangePassword(string username, string oldPassword, string newPassword) { throw new NotImplementedException(); }
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer) { throw new NotImplementedException(); }
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status) { throw new NotImplementedException(); }
public override bool DeleteUser(string username, bool deleteAllRelatedData) { throw new NotImplementedException(); }
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); }
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); }
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords) { throw new NotImplementedException(); }
public interface IEnableUservalidation { bool ValidateUser(string username, string password); }
public interface IAllowUserRetrieval { MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords); MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords); MembershipUser GetUser(object providerUserKey, bool userIsOnline); MembershipUser GetUser(string username, bool userIsOnline); string GetUserNameByEmail(string email); MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords); int GetNumberOfUsersOnline(); }
public interface IProvidePassword { bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer);
bool ChangePassword(string username, string oldPassword, string newPassword); string ResetPassword(string username, string answer);
string GetPassword(string username, string answer); }
Dependency Inversion Principle
Depend on Abstractions Interfaces, not concrete types
Inject Dependencies into Classes
Inversion of ControlHollywood Principle: "Don't call us, We call you"
I tell an object its partners, and not the object chooses its partners
public class WCFSalaryService { private IDBHelper dbHelper = new SQLHelper(); private ILoggerHelper loggerHelper = new FileLogWriter(); private IAuthenticationHelper authenticationHelper = new WebServiceAuth(); private IUserUtility userHelper; private IConnections connectionHelper = new HTTPConnectionHelper();
public WCFSalaryService() { userHelper = new UserHelper(connectionHelper); userHelper.Logger = loggerHelper; dbHelper.Logger = loggerHelper;
// ----- snip snip snip ---- }
// ----- snip snip snip ---- }
private IDBHelper _dbHelper; private ILoggerHelper _loggerHelper; private IAuthenticationHelper _authenticationHelper; private IUserUtility _userHelper; private IConnections _connectionHelper;
public WCFSalaryService( IDBHelper dbHelper, ILoggerHelper loggerHelper, IAuthenticationHelper authenticationHelper, IUserUtility userHelper, IConnections connectionHelper) { _dbHelper = dbHelper; _loggerHelper = loggerHelper; _authenticationHelper = authenticationHelper; _userHelper = userHelper; _connectionHelper = connectionHelper;
private IDBHelper _dbHelper; private ILoggerHelper _loggerHelper = new FileLogWriter(); private IAuthenticationHelper _authenticationHelper = new WebserviceAuth(); private IUserUtility _userHelper; private IConnections _connectionHelper;
public WCFSalaryService( IDBHelper dbHelper, ILoggerHelper loggerHelper, IAuthenticationHelper authenticationHelper, IUserUtility userHelper, IConnections connectionHelper) { _userHelper = new UserHelper(connectionHelper); _userHelper.Logger = loggerHelper;
if (authenticationHelper != null) { _authenticationHelper = authenticationHelper; }
if (dbHelper != null) { _connectionHelper.DbHelper = dbHelper; _authenticationHelper.DbHelper = dbHelper; } else { _connectionHelper.DbHelper = DBHelper.Instance; _authenticationHelper.DbHelper = DBHelper.Instance; }
if (loggerHelper != null) { this._loggerHelper = loggerHelper; }
_userHelper.LoggerHelper = this._loggerHelper; _authenticationHelper.LoggerHelper = this._loggerHelper; _connectionHelper.LoggerHelper = this._loggerHelper; // ----- snip snip snip ---- }
public class SalaryScenario : NinjectModule { public override void Load() { Bind<ILoggerHelper>().To<FileLogWriter>(); Bind<IDBHelper>().To<SQLHelper>(); Bind<IAuthenticationHelper>().To<WebServiceAuth>(); Bind<IUserUtility>().To<UserUtility>(); Bind<IConnections>().To<HTTPConnectionHelper>(); } }
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e) { IKernel kernel = new StandardKernel (new SalaryScenario());
var logger = kernel.Get<ILoggerHelper>(); logger.LogIt("App started up"); }
Solid PrinciplesS.o.l.i.d. Principles
Adam: "What you cant measure you cant improve!"
Measure clean code
• Dependency Diagrams (VS2010)• StyleCop• Code Analysis (VS2010)• Code Metrics (VS2010)• Nitriq
Tools!
• Code Auditor• ReSharper / CodeRush / Refactor Pro• Atomiq• SourceMonitor• NDepend
And more...
More Tools!
No tool can replace a code review
"Writing code a computer can understand is science. Writing code other programmers can understand is an art."
Jason Gorman
Its not easy
From now on...
Readable Code
Tests
Avoid Duplication
• Readable• Tests in place• No duplication
"Wartung"
What is clean code?
• Why code matters• Good and Bad Code• The Broken Window Theory• The Grand Redesign in the Sky• The Boy Scout Rule
• OOP Patterns and Principles• SOLID Principles
• How to measure clean code?• Tools
Summary
Further Reading
Further Reading
Further Reading
Further Reading
VS2010 Code Metrics
http://bit.ly/bda4T1
JB Rainsberger The Four Elements of Simple Design
http://www.jbrains.ca/permalink/the-four-elements-of-simple-design
How to hire a programmer? Have people fix up some smelly code
http://codebetter.com/blogs/karlseguin/archive/2006/12/01/How-to-hire-a-programmer-_2D00_-Part-2-_2D00_-Improve-this-code.aspx
C# Coding Practices
http://www.codeproject.com/KB/cs/CSharp_Coding_Practices.aspx
Object Oriented Principles
http://www.objectmentor.com/omSolutions/oops_what.html
Further Reading
http://www.refactoring.com/
http://refactormycode.com/
All links and slides on
http://blog.gfader.com/
Further Doing
VS2010http://msdn.microsoft.com/en-us/vstudio/
Nitriq & Atomiqhttp://nimblepros.com/products.aspx
SourceMonitorhttp://www.campwoodsw.com/sourcemonitor.html
Ndependhttp://www.ndepend.com/
Tools References
Better SoftwareAn introduction to good code
Giordano Scalzo, 06/05/2009
Thanks toGiordano!
http://creativecommons.org/licenses/by-nc-sa/3.0/
Be a boy scoutLeave the campground cleaner than you found it
Be a boy scoutLeave code cleaner than you found it
All links and slides on
http://blog.gfader.com
Thank you!!!