หากคุณชอบโครงการ clean-code-dotnet หรือถ้ามันช่วยคุณได้โปรดให้ดาวสำหรับที่เก็บนี้ นั่นไม่เพียง แต่ช่วยเสริมสร้างชุมชน. NET ของเรา แต่ยังปรับปรุงทักษะเกี่ยวกับรหัสที่สะอาดสำหรับนักพัฒนา. NET ในทั่วโลก ขอบคุณมาก ?
ตรวจสอบบล็อกของฉันหรือทักทายบน Twitter!
หลักการวิศวกรรมซอฟต์แวร์จากหนังสือ สะอาด ของ Robert C. Martin ดัดแปลงมาจาก. NET/.NET Core นี่ไม่ใช่คู่มือสไตล์ มันเป็นแนวทางในการผลิตซอฟต์แวร์ที่อ่านได้นำกลับมาใช้ซ้ำได้และ refactorable ใน. NET/.NET Core
ไม่ใช่ทุกหลักการในที่นี้ในที่นี้จะต้องปฏิบัติตามอย่างเคร่งครัดและแม้แต่น้อยกว่าจะได้รับการตกลงกันอย่างเป็นสากล เหล่านี้เป็นแนวทางและไม่มีอะไรเพิ่มเติม แต่เป็นสิ่งที่ประมวลผลตลอดหลายปีที่ผ่านมาโดยผู้เขียน โค้ดที่สะอาด
แรงบันดาลใจจากรายการ Clean-Code-Javascript และ Clean-Code-PHP
แย่:
int d ;ดี:
int daySinceModification ;⬆กลับไปด้านบน
ตั้งชื่อตัวแปรเพื่อสะท้อนสิ่งที่ใช้สำหรับ
แย่:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;ดี:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆กลับไปด้านบน
สัญกรณ์ฮังการีปรับปรุงประเภทที่มีอยู่แล้วในการประกาศ นี่ไม่มีจุดหมายเนื่องจาก IDE ที่ทันสมัยจะระบุประเภท
แย่:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;ดี:
int counter ;
string fullName ;
DateTime modifiedDate ;ไม่ควรใช้สัญกรณ์ฮังการีใน Paramaters
แย่:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}ดี:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆กลับไปด้านบน
การใช้อักษรตัวพิมพ์ใหญ่บอกคุณมากมายเกี่ยวกับตัวแปรฟังก์ชั่น ฯลฯ กฎเหล่านี้เป็นอัตนัยดังนั้นทีมของคุณสามารถเลือกสิ่งที่พวกเขาต้องการ ประเด็นก็คือไม่ว่าคุณจะเลือกอะไรก็ตาม
แย่:
const int DAYS_IN_WEEK = 7 ;
const int daysInMonth = 30 ;
var songs = new List < string > { 'Back In Black' , 'Stairway to Heaven' , 'Hey Jude' } ;
var Artists = new List < string > { 'ACDC' , 'Led Zeppelin' , 'The Beatles' } ;
bool EraseDatabase ( ) { }
bool Restore_database ( ) { }
class animal { }
class Alpaca { }ดี:
const int DaysInWeek = 7 ;
const int DaysInMonth = 30 ;
var songs = new List < string > { 'Back In Black' , 'Stairway to Heaven' , 'Hey Jude' } ;
var artists = new List < string > { 'ACDC' , 'Led Zeppelin' , 'The Beatles' } ;
bool EraseDatabase ( ) { }
bool RestoreDatabase ( ) { }
class Animal { }
class Alpaca { }⬆กลับไปด้านบน
จะต้องใช้เวลาในการตรวจสอบความหมายของตัวแปรและฟังก์ชั่นเมื่อไม่สามารถออกเสียงได้
แย่:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}ดี:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆กลับไปด้านบน
ใช้สัญลักษณ์ Camelcase สำหรับตัวแปรและวิธีการพารามิเตอร์
แย่:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}ดี:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆กลับไปด้านบน
ผู้ที่อ่านรหัสของคุณก็เป็นโปรแกรมเมอร์ การตั้งชื่อสิ่งที่ถูกต้องจะช่วยให้ทุกคนอยู่ในหน้าเดียวกัน เราไม่ต้องการใช้เวลาอธิบายให้ทุกคนทราบว่าตัวแปรหรือฟังก์ชั่นมีไว้สำหรับอะไร
ดี
public class SingleObject
{
// create an object of SingleObject
private static SingleObject _instance = new SingleObject ( ) ;
// make the constructor private so that this class cannot be instantiated
private SingleObject ( ) { }
// get the only object available
public static SingleObject GetInstance ( )
{
return _instance ;
}
public string ShowMessage ( )
{
return "Hello World!" ;
}
}
public static void main ( String [ ] args )
{
// illegal construct
// var object = new SingleObject();
// Get the only object available
var singletonObject = SingleObject . GetInstance ( ) ;
// show the message
singletonObject . ShowMessage ( ) ;
}⬆กลับไปด้านบน
มากเกินไปหากมีคำสั่งอื่นสามารถทำให้รหัสยากที่จะติดตาม ชัดเจนดีกว่าโดยปริยาย
แย่:
public bool IsShopOpen ( string day )
{
if ( ! string . IsNullOrEmpty ( day ) )
{
day = day . ToLower ( ) ;
if ( day == "friday" )
{
return true ;
}
else if ( day == "saturday" )
{
return true ;
}
else if ( day == "sunday" )
{
return true ;
}
else
{
return false ;
}
}
else
{
return false ;
}
}ดี:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}แย่:
public long Fibonacci ( int n )
{
if ( n < 50 )
{
if ( n != 0 )
{
if ( n != 1 )
{
return Fibonacci ( n - 1 ) + Fibonacci ( n - 2 ) ;
}
else
{
return 1 ;
}
}
else
{
return 0 ;
}
}
else
{
throw new System . Exception ( "Not supported" ) ;
}
}ดี:
public long Fibonacci ( int n )
{
if ( n == 0 )
{
return 0 ;
}
if ( n == 1 )
{
return 1 ;
}
if ( n > 50 )
{
throw new System . Exception ( "Not supported" ) ;
}
return Fibonacci ( n - 1 ) + Fibonacci ( n - 2 ) ;
}⬆กลับไปด้านบน
อย่าบังคับให้ผู้อ่านรหัสของคุณแปลความหมายของตัวแปร ชัดเจนดีกว่าโดยปริยาย
แย่:
var l = new [ ] { "Austin" , "New York" , "San Francisco" } ;
for ( var i = 0 ; i < l . Count ( ) ; i ++ )
{
var li = l [ i ] ;
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
// Wait, what is `li` for again?
Dispatch ( li ) ;
}ดี:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆กลับไปด้านบน
Strings Magic เป็นค่าสตริงที่ระบุโดยตรงภายในรหัสแอปพลิเคชันที่มีผลกระทบต่อพฤติกรรมของแอปพลิเคชัน บ่อยครั้งที่สตริงดังกล่าวจะจบลงด้วยการทำซ้ำภายในระบบและเนื่องจากพวกเขาไม่สามารถอัปเดตโดยอัตโนมัติโดยใช้เครื่องมือ refactoring พวกเขาจึงกลายเป็นแหล่งที่มาของข้อบกพร่องทั่วไปเมื่อมีการเปลี่ยนแปลงในสายบางอย่าง แต่ไม่ใช่คนอื่น ๆ
แย่
if ( userRole == "Admin" )
{
// logic in here
}ดี
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}การใช้สิ่งนี้เราต้องเปลี่ยนในสถานที่รวมศูนย์และอื่น ๆ จะปรับมัน
⬆กลับไปด้านบน
หากชื่อคลาส/วัตถุของคุณบอกอะไรคุณอย่าทำซ้ำในชื่อตัวแปรของคุณ
แย่:
public class Car
{
public string CarMake { get ; set ; }
public string CarModel { get ; set ; }
public string CarColor { get ; set ; }
//...
}ดี:
public class Car
{
public string Make { get ; set ; }
public string Model { get ; set ; }
public string Color { get ; set ; }
//...
}⬆กลับไปด้านบน
แย่:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;ดี:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆กลับไปด้านบน
แย่:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;ดี:
GetUser ( ) ;⬆กลับไปด้านบน
เราจะอ่านโค้ดมากกว่าที่เราจะเขียน เป็นสิ่งสำคัญที่รหัสที่เราเขียนสามารถอ่านได้และสามารถค้นหาได้ โดยการ ไม่ ตั้งชื่อตัวแปรที่มีความหมายสำหรับการทำความเข้าใจโปรแกรมของเราเราทำร้ายผู้อ่านของเรา ทำให้ชื่อของคุณค้นหาได้
แย่:
// What the heck is data for?
var data = new { Name = "John" , Age = 42 } ;
var stream1 = new MemoryStream ( ) ;
var ser1 = new DataContractJsonSerializer ( typeof ( object ) ) ;
ser1 . WriteObject ( stream1 , data ) ;
stream1 . Position = 0 ;
var sr1 = new StreamReader ( stream1 ) ;
Console . Write ( "JSON form of Data object: " ) ;
Console . WriteLine ( sr1 . ReadToEnd ( ) ) ;ดี:
var person = new Person
{
Name = "John" ,
Age = 42
} ;
var stream2 = new MemoryStream ( ) ;
var ser2 = new DataContractJsonSerializer ( typeof ( Person ) ) ;
ser2 . WriteObject ( stream2 , data ) ;
stream2 . Position = 0 ;
var sr2 = new StreamReader ( stream2 ) ;
Console . Write ( "JSON form of Data object: " ) ;
Console . WriteLine ( sr2 . ReadToEnd ( ) ) ;⬆กลับไปด้านบน
แย่:
var data = new { Name = "John" , Age = 42 , PersonAccess = 4 } ;
// What the heck is 4 for?
if ( data . PersonAccess == 4 )
{
// do edit ...
}ดี:
public enum PersonAccess : int
{
ACCESS_READ = 1 ,
ACCESS_CREATE = 2 ,
ACCESS_UPDATE = 4 ,
ACCESS_DELETE = 8
}
var person = new Person
{
Name = "John" ,
Age = 42 ,
PersonAccess = PersonAccess . ACCESS_CREATE
} ;
if ( person . PersonAccess == PersonAccess . ACCESS_UPDATE )
{
// do edit ...
}⬆กลับไปด้านบน
แย่:
const string Address = "One Infinite Loop, Cupertino 95014" ;
var cityZipCodeRegex = @"/^[^,]+[,\s]+(.+?)s*(d{5})?$/" ;
var matches = Regex . Matches ( Address , cityZipCodeRegex ) ;
if ( matches [ 0 ] . Success == true && matches [ 1 ] . Success == true )
{
SaveCityZipCode ( matches [ 0 ] . Value , matches [ 1 ] . Value ) ;
}ดี:
ลดการพึ่งพา Regex โดยการตั้งชื่อ Subpatterns
const string Address = "One Infinite Loop, Cupertino 95014" ;
var cityZipCodeWithGroupRegex = @"/^[^,]+[,\s]+(?<city>.+?)s*(?<zipCode>d{5})?$/" ;
var matchesWithGroup = Regex . Match ( Address , cityZipCodeWithGroupRegex ) ;
var cityGroup = matchesWithGroup . Groups [ "city" ] ;
var zipCodeGroup = matchesWithGroup . Groups [ "zipCode" ] ;
if ( cityGroup . Success == true && zipCodeGroup . Success == true )
{
SaveCityZipCode ( cityGroup . Value , zipCodeGroup . Value ) ;
}⬆กลับไปด้านบน
ไม่ดี:
สิ่งนี้ไม่ดีเพราะ breweryName สามารถเป็น NULL ได้
ความคิดเห็นนี้เข้าใจได้ดีกว่ารุ่นก่อนหน้า แต่จะควบคุมค่าของตัวแปรได้ดีกว่า
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}ดี:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆กลับไปด้านบน
ฟังก์ชั่นสร้างผลข้างเคียงหากทำสิ่งใดนอกจากใช้ค่าและส่งคืนค่าหรือค่าอื่น ผลข้างเคียงอาจเขียนไปยังไฟล์ปรับเปลี่ยนตัวแปรทั่วโลกหรือการเดินสายเงินทั้งหมดของคุณไปยังคนแปลกหน้าโดยไม่ตั้งใจ
ตอนนี้คุณจำเป็นต้องมีผลข้างเคียงในโปรแกรมในบางโอกาส เช่นเดียวกับตัวอย่างก่อนหน้านี้คุณอาจต้องเขียนไปยังไฟล์ สิ่งที่คุณต้องการทำคือการรวมศูนย์ไว้ที่ที่คุณทำสิ่งนี้ ไม่มีฟังก์ชั่นและคลาสหลายอย่างที่เขียนไปยังไฟล์เฉพาะ มีหนึ่งบริการที่ทำ หนึ่งเดียวเท่านั้น
ประเด็นหลักคือการหลีกเลี่ยงข้อผิดพลาดที่พบบ่อยเช่นการแบ่งปันสถานะระหว่างวัตถุโดยไม่มีโครงสร้างใด ๆ โดยใช้ชนิดข้อมูลที่ไม่แน่นอนซึ่งสามารถเขียนได้โดยสิ่งใดและไม่รวมศูนย์ที่ผลข้างเคียงของคุณเกิดขึ้น หากคุณสามารถทำสิ่งนี้ได้คุณจะมีความสุขมากกว่าโปรแกรมเมอร์ส่วนใหญ่
แย่:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
var name = "Ryan McDermott" ;
public void SplitAndEnrichFullName ( )
{
var temp = name . Split ( " " ) ;
name = $ "His first name is { temp [ 0 ] } , and his last name is { temp [ 1 ] } " ; // side effect
}
SplitAndEnrichFullName ( ) ;
Console . WriteLine ( name ) ; // His first name is Ryan, and his last name is McDermottดี:
public string SplitAndEnrichFullName ( string name )
{
var temp = name . Split ( " " ) ;
return $ "His first name is { temp [ 0 ] } , and his last name is { temp [ 1 ] } " ;
}
var name = "Ryan McDermott" ;
var fullName = SplitAndEnrichFullName ( name ) ;
Console . WriteLine ( name ) ; // Ryan McDermott
Console . WriteLine ( fullName ) ; // His first name is Ryan, and his last name is McDermott⬆กลับไปด้านบน
แย่:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}ดี:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆กลับไปด้านบน
ดูเหมือนว่าเป็นงานที่เป็นไปไม่ได้ เมื่อได้ยินเรื่องนี้ครั้งแรกคนส่วนใหญ่พูดว่า "ฉันควรจะทำอะไรโดยไม่มีคำสั่ง if เป็นอย่างไร" คำตอบคือคุณสามารถใช้ polymorphism เพื่อให้ได้งานเดียวกันในหลายกรณี คำถามที่สองมักจะเป็น "ดีมาก แต่ทำไมฉันถึงอยากทำอย่างนั้น?" คำตอบคือแนวคิดรหัสที่สะอาดก่อนหน้านี้ที่เราได้เรียนรู้: ฟังก์ชั่นควรทำเพียงสิ่งเดียว เมื่อคุณมีคลาสและฟังก์ชั่นที่มี if คำสั่งคุณกำลังบอกผู้ใช้ของคุณว่าฟังก์ชั่นของคุณทำมากกว่าหนึ่งสิ่ง จำไว้ว่าทำสิ่งเดียว
แย่:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}ดี:
interface IAirplane
{
// ...
double GetCruisingAltitude ( ) ;
}
class Boeing777 : IAirplane
{
// ...
public double GetCruisingAltitude ( )
{
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
}
}
class AirForceOne : IAirplane
{
// ...
public double GetCruisingAltitude ( )
{
return GetMaxAltitude ( ) ;
}
}
class Cessna : IAirplane
{
// ...
public double GetCruisingAltitude ( )
{
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}⬆กลับไปด้านบน
แย่:
public Path TravelToTexas ( object vehicle )
{
if ( vehicle . GetType ( ) == typeof ( Bicycle ) )
{
( vehicle as Bicycle ) . PeddleTo ( new Location ( "texas" ) ) ;
}
else if ( vehicle . GetType ( ) == typeof ( Car ) )
{
( vehicle as Car ) . DriveTo ( new Location ( "texas" ) ) ;
}
}ดี:
public Path TravelToTexas ( Traveler vehicle )
{
vehicle . TravelTo ( new Location ( "texas" ) ) ;
}หรือ
// pattern matching
public Path TravelToTexas ( object vehicle )
{
if ( vehicle is Bicycle bicycle )
{
bicycle . PeddleTo ( new Location ( "texas" ) ) ;
}
else if ( vehicle is Car car )
{
car . DriveTo ( new Location ( "texas" ) ) ;
}
}⬆กลับไปด้านบน
แย่:
public int Combine ( dynamic val1 , dynamic val2 )
{
int value ;
if ( ! int . TryParse ( val1 , out value ) || ! int . TryParse ( val2 , out value ) )
{
throw new Exception ( 'Must be of type Number' ) ;
}
return val1 + val2 ;
}ดี:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆กลับไปด้านบน
ธงบ่งชี้ว่าวิธีนี้มีความรับผิดชอบมากกว่าหนึ่งอย่าง เป็นการดีที่สุดถ้าวิธีนี้มีความรับผิดชอบเพียงอย่างเดียว แบ่งวิธีเป็นสองถ้าพารามิเตอร์บูลีนเพิ่มความรับผิดชอบหลายอย่างในวิธีการ
แย่:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}ดี:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆กลับไปด้านบน
การก่อมลพิษรอบโลกเป็นวิธีปฏิบัติที่ไม่ดีในหลาย ๆ ภาษาเพราะคุณสามารถปะทะกับห้องสมุดอื่นและผู้ใช้ API ของคุณจะไม่เป็นคนที่ไม่ดีจนกว่าพวกเขาจะได้รับข้อยกเว้นในการผลิต ลองคิดดูตัวอย่าง: ถ้าคุณต้องการมีอาร์เรย์การกำหนดค่า คุณสามารถเขียนฟังก์ชั่นทั่วโลกเช่น Config() แต่มันสามารถปะทะกับห้องสมุดอื่นที่พยายามทำสิ่งเดียวกัน
แย่:
public Dictionary < string , string > Config ( )
{
return new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ;
}ดี:
class Configuration
{
private Dictionary < string , string > _configuration ;
public Configuration ( Dictionary < string , string > configuration )
{
_configuration = configuration ;
}
public string [ ] Get ( string key )
{
return _configuration . ContainsKey ( key ) ? _configuration [ key ] : null ;
}
} โหลดการกำหนดค่าและสร้างอินสแตนซ์ของคลาส Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; และตอนนี้คุณต้องใช้อินสแตนซ์ของ Configuration ในแอปพลิเคชันของคุณ
⬆กลับไปด้านบน
ซิงเกิลตันเป็นต่อต้านรูปแบบ ถอดความจาก Brian Button:
นอกจากนี้ยังมีความคิดที่ดีมากโดย Misko เกี่ยวกับรากของปัญหา
แย่:
class DBConnection
{
private static DBConnection _instance ;
private DBConnection ( )
{
// ...
}
public static GetInstance ( )
{
if ( _instance == null )
{
_instance = new DBConnection ( ) ;
}
return _instance ;
}
// ...
}
var singleton = DBConnection . GetInstance ( ) ;ดี:
class DBConnection
{
public DBConnection ( IOptions < DbConnectionOption > options )
{
// ...
}
// ...
} สร้างอินสแตนซ์ของคลาส DBConnection และกำหนดค่าด้วยรูปแบบตัวเลือก
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; และตอนนี้คุณต้องใช้อินสแตนซ์ของ DBConnection ในแอปพลิเคชันของคุณ
⬆กลับไปด้านบน
การ จำกัด จำนวนพารามิเตอร์ฟังก์ชั่นมีความสำคัญอย่างไม่น่าเชื่อเพราะทำให้การทดสอบฟังก์ชั่นของคุณง่ายขึ้น การมีโอกาสในการระเบิดมากกว่าสามครั้งซึ่งคุณต้องทดสอบกรณีต่าง ๆ มากมายกับแต่ละอาร์กิวเมนต์แยกต่างหาก
อาร์กิวเมนต์เป็นศูนย์เป็นกรณีที่เหมาะ หนึ่งหรือสองข้อโต้แย้งก็โอเคและควรหลีกเลี่ยงสามข้อ อะไรที่มากกว่านั้นควรรวมเข้าด้วยกัน โดยปกติหากคุณมีอาร์กิวเมนต์มากกว่าสองข้อฟังก์ชั่นของคุณพยายามทำมากเกินไป ในกรณีที่ไม่ใช่ส่วนใหญ่วัตถุระดับสูงจะพอเพียงเป็นอาร์กิวเมนต์
แย่:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}ดี:
public class MenuConfig
{
public string Title { get ; set ; }
public string Body { get ; set ; }
public string ButtonText { get ; set ; }
public bool Cancellable { get ; set ; }
}
var config = new MenuConfig
{
Title = "Foo" ,
Body = "Bar" ,
ButtonText = "Baz" ,
Cancellable = true
} ;
public void CreateMenu ( MenuConfig config )
{
// ...
}⬆กลับไปด้านบน
นี่เป็นกฎที่สำคัญที่สุดในวิศวกรรมซอฟต์แวร์ เมื่อฟังก์ชั่นทำมากกว่าหนึ่งสิ่งพวกเขาจะยากที่จะเขียนทดสอบและเหตุผลเกี่ยวกับ เมื่อคุณสามารถแยกฟังก์ชั่นเพื่อการกระทำเพียงครั้งเดียวพวกเขาสามารถ refactored ได้อย่างง่ายดายและรหัสของคุณจะอ่านสะอาดกว่ามาก หากคุณไม่ได้รับสิ่งอื่นใดห่างจากคู่มือนี้นอกจากนี้คุณจะอยู่ข้างหน้านักพัฒนาหลายคน
แย่:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}ดี:
public void SendEmailToListOfClients ( string [ ] clients )
{
var activeClients = GetActiveClients ( clients ) ;
// Do some logic
}
public List < Client > GetActiveClients ( string [ ] clients )
{
return db . Find ( clients ) . Where ( s => s . Status == "Active" ) ;
}⬆กลับไปด้านบน
แย่:
public class Email
{
//...
public void Handle ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// What is this? A handle for the message? Are we writing to a file now?
message . Handle ( ) ;ดี:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆กลับไปด้านบน
ยังไม่เสร็จ
เมื่อคุณมีนามธรรมมากกว่าหนึ่งระดับฟังก์ชั่นของคุณมักจะทำมากเกินไป การแยกฟังก์ชั่นนำไปสู่การใช้ซ้ำและการทดสอบที่ง่ายขึ้น
แย่:
public string ParseBetterJSAlternative ( string code )
{
var regexes = [
// ...
] ;
var statements = explode ( " " , code ) ;
var tokens = new string [ ] { } ;
foreach ( var regex in regexes )
{
foreach ( var statement in statements )
{
// ...
}
}
var ast = new string [ ] { } ;
foreach ( var token in tokens )
{
// lex...
}
foreach ( var node in ast )
{
// parse...
}
}ไม่ดีเช่นกัน:
เราได้ทำฟังก์ชั่นบางอย่าง แต่ฟังก์ชั่น ParseBetterJSAlternative() ยังคงซับซ้อนและไม่สามารถทดสอบได้
public string Tokenize ( string code )
{
var regexes = new string [ ]
{
// ...
} ;
var statements = explode ( " " , code ) ;
var tokens = new string [ ] { } ;
foreach ( var regex in regexes )
{
foreach ( var statement in statements )
{
tokens [ ] = /* ... */ ;
}
}
return tokens ;
}
public string Lexer ( string [ ] tokens )
{
var ast = new string [ ] { } ;
foreach ( var token in tokens )
{
ast [ ] = /* ... */ ;
}
return ast ;
}
public string ParseBetterJSAlternative ( string code )
{
var tokens = Tokenize ( code ) ;
var ast = Lexer ( tokens ) ;
foreach ( var node in ast )
{
// parse...
}
}ดี:
ทางออกที่ดีที่สุดคือการย้ายออกจากการพึ่งพาของฟังก์ชั่น ParseBetterJSAlternative()
class Tokenizer
{
public string Tokenize ( string code )
{
var regexes = new string [ ] {
// ...
} ;
var statements = explode ( " " , code ) ;
var tokens = new string [ ] { } ;
foreach ( var regex in regexes )
{
foreach ( var statement in statements )
{
tokens [ ] = /* ... */ ;
}
}
return tokens ;
}
}
class Lexer
{
public string Lexify ( string [ ] tokens )
{
var ast = new [ ] { } ;
foreach ( var token in tokens )
{
ast [ ] = /* ... */ ;
}
return ast ;
}
}
class BetterJSAlternative
{
private string _tokenizer ;
private string _lexer ;
public BetterJSAlternative ( Tokenizer tokenizer , Lexer lexer )
{
_tokenizer = tokenizer ;
_lexer = lexer ;
}
public string Parse ( string code )
{
var tokens = _tokenizer . Tokenize ( code ) ;
var ast = _lexer . Lexify ( tokens ) ;
foreach ( var node in ast )
{
// parse...
}
}
}⬆กลับไปด้านบน
หากฟังก์ชั่นเรียกอื่นให้ปิดฟังก์ชั่นเหล่านั้นในแนวตั้งในไฟล์ต้นฉบับ เป็นการดีที่จะให้ผู้โทรอยู่เหนือ Callee เรามักจะอ่านรหัสจากบนลงล่างเช่นหนังสือพิมพ์ ด้วยเหตุนี้ทำให้รหัสของคุณอ่านแบบนั้น
แย่:
class PerformanceReview
{
private readonly Employee _employee ;
public PerformanceReview ( Employee employee )
{
_employee = employee ;
}
private IEnumerable < PeersData > LookupPeers ( )
{
return db . lookup ( _employee , 'peers' ) ;
}
private ManagerData LookupManager ( )
{
return db . lookup ( _employee , 'manager' ) ;
}
private IEnumerable < PeerReviews > GetPeerReviews ( )
{
var peers = LookupPeers ( ) ;
// ...
}
public PerfReviewData PerfReview ( )
{
GetPeerReviews ( ) ;
GetManagerReview ( ) ;
GetSelfReview ( ) ;
}
public ManagerData GetManagerReview ( )
{
var manager = LookupManager ( ) ;
}
public EmployeeData GetSelfReview ( )
{
// ...
}
}
var review = new PerformanceReview ( employee ) ;
review . PerfReview ( ) ;ดี:
class PerformanceReview
{
private readonly Employee _employee ;
public PerformanceReview ( Employee employee )
{
_employee = employee ;
}
public PerfReviewData PerfReview ( )
{
GetPeerReviews ( ) ;
GetManagerReview ( ) ;
GetSelfReview ( ) ;
}
private IEnumerable < PeerReviews > GetPeerReviews ( )
{
var peers = LookupPeers ( ) ;
// ...
}
private IEnumerable < PeersData > LookupPeers ( )
{
return db . lookup ( _employee , 'peers' ) ;
}
private ManagerData GetManagerReview ( )
{
var manager = LookupManager ( ) ;
return manager ;
}
private ManagerData LookupManager ( )
{
return db . lookup ( _employee , 'manager' ) ;
}
private EmployeeData GetSelfReview ( )
{
// ...
}
}
var review = new PerformanceReview ( employee ) ;
review . PerfReview ( ) ;⬆กลับไปด้านบน
แย่:
if ( article . state == "published" )
{
// ...
}ดี:
if ( article . IsPublished ( ) )
{
// ...
}⬆กลับไปด้านบน
รหัส Dead นั้นไม่ดีเท่ากับรหัสที่ซ้ำกัน ไม่มีเหตุผลที่จะเก็บไว้ใน codebase ของคุณ ถ้ามันไม่ถูกเรียกให้กำจัดมัน! มันจะยังคงปลอดภัยในประวัติเวอร์ชันของคุณหากคุณยังต้องการ
แย่:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;ดี:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆กลับไปด้านบน
ใน C# / VB.NET คุณสามารถตั้งค่าคำหลัก public protected และ private สำหรับวิธีการ การใช้มันคุณสามารถควบคุมการปรับเปลี่ยนคุณสมบัติบนวัตถุ
setนอกจากนี้นี่เป็นส่วนหนึ่งของหลักการเปิด/ปิดจากหลักการออกแบบเชิงวัตถุ
แย่:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;ดี:
class BankAccount
{
private double _balance = 0.0D ;
pubic double Balance {
get {
return _balance ;
}
}
public BankAccount ( balance = 1000 )
{
_balance = balance ;
}
public void WithdrawBalance ( int amount )
{
if ( amount > _balance )
{
throw new Exception ( 'Amount greater than available balance . ' ) ;
}
_balance -= amount ;
}
public void DepositBalance ( int amount )
{
_balance += amount ;
}
}
var bankAccount = new BankAccount ( ) ;
// Buy shoes...
bankAccount . WithdrawBalance ( price ) ;
// Get balance
balance = bankAccount . Balance ;⬆กลับไปด้านบน
แย่:
class Employee
{
public string Name { get ; set ; }
public Employee ( string name )
{
Name = name ;
}
}
var employee = new Employee ( "John Doe" ) ;
Console . WriteLine ( employee . Name ) ; // Employee name: John Doeดี:
class Employee
{
public string Name { get ; }
public Employee ( string name )
{
Name = name ;
}
}
var employee = new Employee ( "John Doe" ) ;
Console . WriteLine ( employee . Name ) ; // Employee name: John Doe⬆กลับไปด้านบน
รูปแบบนี้มีประโยชน์มากและใช้กันทั่วไปในห้องสมุดหลายแห่ง ช่วยให้รหัสของคุณแสดงออกได้และมีคำย้ำน้อยลง ด้วยเหตุผลดังกล่าวให้ใช้วิธีการผูกมัดและดูว่ารหัสของคุณจะสะอาดแค่ไหน
ดี:
public static class ListExtensions
{
public static List < T > FluentAdd < T > ( this List < T > list , T item )
{
list . Add ( item ) ;
return list ;
}
public static List < T > FluentClear < T > ( this List < T > list )
{
list . Clear ( ) ;
return list ;
}
public static List < T > FluentForEach < T > ( this List < T > list , Action < T > action )
{
list . ForEach ( action ) ;
return list ;
}
public static List < T > FluentInsert < T > ( this List < T > list , int index , T item )
{
list . Insert ( index , item ) ;
return list ;
}
public static List < T > FluentRemoveAt < T > ( this List < T > list , int index )
{
list . RemoveAt ( index ) ;
return list ;
}
public static List < T > FluentReverse < T > ( this List < T > list )
{
list . Reverse ( ) ;
return list ;
}
}
internal static void ListFluentExtensions ( )
{
var list = new List < int > ( ) { 1 , 2 , 3 , 4 , 5 }
. FluentAdd ( 1 )
. FluentInsert ( 0 , 0 )
. FluentRemoveAt ( 1 )
. FluentReverse ( )
. FluentForEach ( value => value . WriteLine ( ) )
. FluentClear ( ) ;
}⬆กลับไปด้านบน
ตามที่ระบุไว้อย่างมีชื่อเสียงใน รูปแบบการออกแบบ โดยแก๊งค์ของสี่คุณควรชอบองค์ประกอบมากกว่ามรดกที่คุณสามารถทำได้ มีเหตุผลที่ดีมากมายในการใช้มรดกและเหตุผลที่ดีมากมายในการใช้องค์ประกอบ
ประเด็นหลักสำหรับ maxim นี้คือถ้าจิตใจของคุณไปโดยสัญชาตญาณเพื่อมรดกลองคิดว่าการแต่งเพลงสามารถสร้างแบบจำลองปัญหาของคุณได้ดีขึ้นหรือไม่ ในบางกรณีมันสามารถ
คุณอาจสงสัยว่า "ฉันควรใช้มรดกเมื่อไหร่" ขึ้นอยู่กับปัญหาของคุณอยู่ในมือ แต่นี่เป็นรายการที่ดีของการสืบทอดที่สมเหตุสมผลมากกว่าองค์ประกอบ:
แย่:
class Employee
{
private string Name { get ; set ; }
private string Email { get ; set ; }
public Employee ( string name , string email )
{
Name = name ;
Email = email ;
}
// ...
}
// Bad because Employees "have" tax data.
// EmployeeTaxData is not a type of Employee
class EmployeeTaxData : Employee
{
private string Name { get ; }
private string Email { get ; }
public EmployeeTaxData ( string name , string email , string ssn , string salary )
{
// ...
}
// ...
}ดี:
class EmployeeTaxData
{
public string Ssn { get ; }
public string Salary { get ; }
public EmployeeTaxData ( string ssn , string salary )
{
Ssn = ssn ;
Salary = salary ;
}
// ...
}
class Employee
{
public string Name { get ; }
public string Email { get ; }
public EmployeeTaxData TaxData { get ; }
public Employee ( string name , string email )
{
Name = name ;
Email = email ;
}
public void SetTax ( string ssn , double salary )
{
TaxData = new EmployeeTaxData ( ssn , salary ) ;
}
// ...
}⬆กลับไปด้านบน
Solid เป็นตัวย่อของการช่วยจำที่แนะนำโดย Michael Feathers สำหรับหลักการห้าประการแรกที่ได้รับการตั้งชื่อโดย Robert Martin ซึ่งหมายถึงหลักการพื้นฐานห้าประการของการเขียนโปรแกรมและการออกแบบเชิงวัตถุ
ตามที่ระบุไว้ในรหัสที่สะอาด "ไม่ควรมีเหตุผลมากกว่าหนึ่งข้อที่ชั้นเรียนเปลี่ยน" มันดึงดูดใจที่จะติดคลาสที่มีฟังก์ชั่นมากมายเช่นเมื่อคุณสามารถทานกระเป๋าเดินทางหนึ่งใบในเที่ยวบินของคุณ ปัญหาเกี่ยวกับเรื่องนี้คือชั้นเรียนของคุณจะไม่เหนียวแน่นในเชิงแนวคิดและจะให้เหตุผลหลายประการในการเปลี่ยนแปลง การลดจำนวนครั้งที่คุณต้องเปลี่ยนคลาสเป็นสิ่งสำคัญ
เป็นสิ่งสำคัญเพราะถ้าฟังก์ชั่นมากเกินไปอยู่ในคลาสเดียวและคุณปรับเปลี่ยนชิ้นส่วนของมันอาจเป็นเรื่องยากที่จะเข้าใจว่าจะส่งผลกระทบต่อโมดูลอื่น ๆ ใน codebase ของคุณอย่างไร
แย่:
class UserSettings
{
private User User ;
public UserSettings ( User user )
{
User = user ;
}
public void ChangeSettings ( Settings settings )
{
if ( verifyCredentials ( ) )
{
// ...
}
}
private bool VerifyCredentials ( )
{
// ...
}
}ดี:
class UserAuth
{
private User User ;
public UserAuth ( User user )
{
User = user ;
}
public bool VerifyCredentials ( )
{
// ...
}
}
class UserSettings
{
private User User ;
private UserAuth Auth ;
public UserSettings ( User user )
{
User = user ;
Auth = new UserAuth ( user ) ;
}
public void ChangeSettings ( Settings settings )
{
if ( Auth . VerifyCredentials ( ) )
{
// ...
}
}
}⬆กลับไปด้านบน
ตามที่ระบุไว้โดย Bertrand Meyer "เอนทิตีซอฟต์แวร์ (คลาสโมดูลฟังก์ชั่น ฯลฯ ) ควรเปิดสำหรับการขยาย แต่ปิดสำหรับการปรับเปลี่ยน" นั่นหมายความว่าอย่างไร? หลักการนี้โดยทั่วไประบุว่าคุณควรอนุญาตให้ผู้ใช้เพิ่มฟังก์ชันใหม่โดยไม่ต้องเปลี่ยนรหัสที่มีอยู่
แย่:
abstract class AdapterBase
{
protected string Name ;
public string GetName ( )
{
return Name ;
}
}
class AjaxAdapter : AdapterBase
{
public AjaxAdapter ( )
{
Name = "ajaxAdapter" ;
}
}
class NodeAdapter : AdapterBase
{
public NodeAdapter ( )
{
Name = "nodeAdapter" ;
}
}
class HttpRequester : AdapterBase
{
private readonly AdapterBase Adapter ;
public HttpRequester ( AdapterBase adapter )
{
Adapter = adapter ;
}
public bool Fetch ( string url )
{
var adapterName = Adapter . GetName ( ) ;
if ( adapterName == "ajaxAdapter" )
{
return MakeAjaxCall ( url ) ;
}
else if ( adapterName == "httpNodeAdapter" )
{
return MakeHttpCall ( url ) ;
}
}
private bool MakeAjaxCall ( string url )
{
// request and return promise
}
private bool MakeHttpCall ( string url )
{
// request and return promise
}
}ดี:
interface IAdapter
{
bool Request ( string url ) ;
}
class AjaxAdapter : IAdapter
{
public bool Request ( string url )
{
// request and return promise
}
}
class NodeAdapter : IAdapter
{
public bool Request ( string url )
{
// request and return promise
}
}
class HttpRequester
{
private readonly IAdapter Adapter ;
public HttpRequester ( IAdapter adapter )
{
Adapter = adapter ;
}
public bool Fetch ( string url )
{
return Adapter . Request ( url ) ;
}
}⬆กลับไปด้านบน
นี่เป็นคำที่น่ากลัวสำหรับแนวคิดที่ง่ายมาก มันถูกกำหนดอย่างเป็นทางการว่า "ถ้า s เป็นชนิดย่อยของ t ดังนั้นวัตถุประเภท t อาจถูกแทนที่ด้วยวัตถุประเภท S (เช่นวัตถุประเภท S อาจแทนที่วัตถุประเภท T) โดยไม่ต้องเปลี่ยนคุณสมบัติใด ๆ ที่ต้องการของโปรแกรมนั้น (ความถูกต้องงานที่ดำเนินการ ฯลฯ )" นั่นเป็นคำจำกัดความที่น่ากลัวยิ่งขึ้น
คำอธิบายที่ดีที่สุดสำหรับเรื่องนี้คือถ้าคุณมีคลาสหลักและชั้นเรียนเด็กจากนั้นคลาสฐานและชั้นเด็กสามารถใช้แทนกันได้โดยไม่ได้รับผลลัพธ์ที่ไม่ถูกต้อง สิ่งนี้อาจทำให้สับสนดังนั้นลองมาดูตัวอย่างสี่เหลี่ยมจัตุรัสแบบคลาสสิก ในทางคณิตศาสตร์สี่เหลี่ยมเป็นสี่เหลี่ยมผืนผ้า แต่ถ้าคุณทำแบบจำลองโดยใช้ความสัมพันธ์ "IS-A" ผ่านการสืบทอดคุณจะมีปัญหาอย่างรวดเร็ว
แย่:
class Rectangle
{
protected double Width = 0 ;
protected double Height = 0 ;
public Drawable Render ( double area )
{
// ...
}
public void SetWidth ( double width )
{
Width = width ;
}
public void SetHeight ( double height )
{
Height = height ;
}
public double GetArea ( )
{
return Width * Height ;
}
}
class Square : Rectangle
{
public double SetWidth ( double width )
{
Width = Height = width ;
}
public double SetHeight ( double height )
{
Width = Height = height ;
}
}
Drawable RenderLargeRectangles ( Rectangle rectangles )
{
foreach ( rectangle in rectangles )
{
rectangle . SetWidth ( 4 ) ;
rectangle . SetHeight ( 5 ) ;
var area = rectangle . GetArea ( ) ; // BAD: Will return 25 for Square. Should be 20.
rectangle . Render ( area ) ;
}
}
var rectangles = new [ ] { new Rectangle ( ) , new Rectangle ( ) , new Square ( ) } ;
RenderLargeRectangles ( rectangles ) ;ดี:
abstract class ShapeBase
{
protected double Width = 0 ;
protected double Height = 0 ;
abstract public double GetArea ( ) ;
public Drawable Render ( double area )
{
// ...
}
}
class Rectangle : ShapeBase
{
public void SetWidth ( double width )
{
Width = width ;
}
public void SetHeight ( double height )
{
Height = height ;
}
public double GetArea ( )
{
return Width * Height ;
}
}
class Square : ShapeBase
{
private double Length = 0 ;
public double SetLength ( double length )
{
Length = length ;
}
public double GetArea ( )
{
return Math . Pow ( Length , 2 ) ;
}
}
Drawable RenderLargeRectangles ( Rectangle rectangles )
{
foreach ( rectangle in rectangles )
{
if ( rectangle is Square )
{
rectangle . SetLength ( 5 ) ;
}
else if ( rectangle is Rectangle )
{
rectangle . SetWidth ( 4 ) ;
rectangle . SetHeight ( 5 ) ;
}
var area = rectangle . GetArea ( ) ;
rectangle . Render ( area ) ;
}
}
var shapes = new [ ] { new Rectangle ( ) , new Rectangle ( ) , new Square ( ) } ;
RenderLargeRectangles ( shapes ) ;⬆กลับไปด้านบน
ISP ระบุว่า "ลูกค้าไม่ควรถูกบังคับให้ต้องพึ่งพาอินเทอร์เฟซที่ไม่ได้ใช้"
ตัวอย่างที่ดีในการดูที่แสดงให้เห็นถึงหลักการนี้สำหรับชั้นเรียนที่ต้องใช้วัตถุการตั้งค่าขนาดใหญ่ ไม่ต้องการให้ลูกค้าตั้งค่าตัวเลือกจำนวนมากเป็นประโยชน์เพราะส่วนใหญ่พวกเขาไม่ต้องการการตั้งค่าทั้งหมด การทำให้เป็นทางเลือกช่วยป้องกันการมี "อินเทอร์เฟซไขมัน"
แย่:
public interface IEmployee
{
void Work ( ) ;
void Eat ( ) ;
}
public class Human : IEmployee
{
public void Work ( )
{
// ....working
}
public void Eat ( )
{
// ...... eating in lunch break
}
}
public class Robot : IEmployee
{
public void Work ( )
{
//.... working much more
}
public void Eat ( )
{
//.... robot can't eat, but it must implement this method
}
}ดี:
ไม่ใช่คนงานทุกคนเป็นพนักงาน แต่พนักงานทุกคนเป็นคนงาน
public interface IWorkable
{
void Work ( ) ;
}
public interface IFeedable
{
void Eat ( ) ;
}
public interface IEmployee : IFeedable , IWorkable
{
}
public class Human : IEmployee
{
public void Work ( )
{
// ....working
}
public void Eat ( )
{
//.... eating in lunch break
}
}
// robot can only work
public class Robot : IWorkable
{
public void Work ( )
{
// ....working
}
}⬆กลับไปด้านบน
หลักการนี้ระบุสองสิ่งสำคัญ:
นี่อาจเป็นเรื่องยากที่จะเข้าใจในตอนแรก แต่ถ้าคุณทำงานกับ. NET/.NET Core Framework คุณเคยเห็นการใช้หลักการนี้ในรูปแบบของการฉีดพึ่งพา (DI) ในขณะที่พวกเขาไม่ได้เป็นแนวคิดที่เหมือนกัน DIP จะช่วยให้โมดูลระดับสูงไม่รู้จักรายละเอียดของโมดูลระดับต่ำและตั้งค่า มันสามารถทำสิ่งนี้ได้ผ่าน DI ประโยชน์อย่างมากของเรื่องนี้คือมันช่วยลดการมีเพศสัมพันธ์ระหว่างโมดูล การมีเพศสัมพันธ์เป็นรูปแบบการพัฒนาที่แย่มากเพราะมันทำให้รหัสของคุณยากที่จะ refactor
แย่:
public abstract class EmployeeBase
{
protected virtual void Work ( )
{
// ....working
}
}
public class Human : EmployeeBase
{
public override void Work ( )
{
//.... working much more
}
}
public class Robot : EmployeeBase
{
public override void Work ( )
{
//.... working much, much more
}
}
public class Manager
{
private readonly Robot _robot ;
private readonly Human _human ;
public Manager ( Robot robot , Human human )
{
_robot = robot ;
_human = human ;
}
public void Manage ( )
{
_robot . Work ( ) ;
_human . Work ( ) ;
}
}ดี:
public interface IEmployee
{
void Work ( ) ;
}
public class Human : IEmployee
{
public void Work ( )
{
// ....working
}
}
public class Robot : IEmployee
{
public void Work ( )
{
//.... working much more
}
}
public class Manager
{
private readonly IEnumerable < IEmployee > _employees ;
public Manager ( IEnumerable < IEmployee > employees )
{
_employees = employees ;
}
public void Manage ( )
{
foreach ( var employee in _employees )
{
_employee . Work ( ) ;
}
}
}⬆กลับไปด้านบน
พยายามสังเกตหลักการแห้ง
ทำอย่างดีที่สุดเพื่อหลีกเลี่ยงรหัสที่ซ้ำกัน รหัสที่ซ้ำกันนั้นไม่ดีเพราะหมายความว่ามีมากกว่าหนึ่งที่ในการเปลี่ยนแปลงบางสิ่งบางอย่างหากคุณต้องการเปลี่ยนตรรกะบางอย่าง
ลองนึกภาพถ้าคุณใช้ร้านอาหารและติดตามสินค้าคงคลังของคุณ: มะเขือเทศหัวหอม, กระเทียม, เครื่องเทศ ฯลฯ หากคุณมีหลายรายการที่คุณเก็บไว้ทุกอย่างจะต้องได้รับการปรับปรุงเมื่อคุณเสิร์ฟอาหารกับมะเขือเทศ หากคุณมีเพียงรายการเดียวมีเพียงสถานที่เดียวที่จะอัปเดต!
บ่อยครั้งที่คุณมีรหัสที่ซ้ำกันเพราะคุณมีสองสิ่งที่แตกต่างกันเล็กน้อยขึ้นไปซึ่งมีส่วนร่วมมากเหมือนกัน แต่ความแตกต่างของพวกเขาบังคับให้คุณมีฟังก์ชั่นแยกกันสองอย่างขึ้นไปที่ทำสิ่งเดียวกันมาก การลบรหัสที่ซ้ำกันหมายถึงการสร้างสิ่งที่เป็นนามธรรมที่สามารถจัดการชุดของสิ่งต่าง ๆ นี้ด้วยฟังก์ชั่น/โมดูล/คลาสเพียงหนึ่งเดียว
การได้รับสิทธิที่เป็นนามธรรมเป็นสิ่งสำคัญนั่นคือเหตุผลที่คุณควรทำตามหลักการที่เป็นของแข็งที่วางไว้ในส่วนชั้นเรียน abstractions ที่ไม่ดีอาจเลวร้ายยิ่งกว่ารหัสที่ซ้ำกันดังนั้นโปรดระวัง! ต้องพูดแบบนี้ถ้าคุณสามารถสร้างสิ่งที่เป็นนามธรรมให้ทำ! อย่าทำซ้ำตัวเองมิฉะนั้นคุณจะพบว่าตัวเองกำลังอัปเดตหลายสถานที่ทุกเวลาที่คุณต้องการเปลี่ยนสิ่งหนึ่ง
แย่:
public List < EmployeeData > ShowDeveloperList ( Developers developers )
{
foreach ( var developers in developer )
{
var expectedSalary = developer . CalculateExpectedSalary ( ) ;
var experience = developer . GetExperience ( ) ;
var githubLink = developer . GetGithubLink ( ) ;
var data = new [ ] {
expectedSalary ,
experience ,
githubLink
} ;
Render ( data ) ;
}
}
public List < ManagerData > ShowManagerList ( Manager managers )
{
foreach ( var manager in managers )
{
var expectedSalary = manager . CalculateExpectedSalary ( ) ;
var experience = manager . GetExperience ( ) ;
var githubLink = manager . GetGithubLink ( ) ;
var data =
new [ ] {
expectedSalary ,
experience ,
githubLink
} ;
render ( data ) ;
}
}ดี:
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
var expectedSalary = employees . CalculateExpectedSalary ( ) ;
var experience = employees . GetExperience ( ) ;
var githubLink = employees . GetGithubLink ( ) ;
var data =
new [ ] {
expectedSalary ,
experience ,
githubLink
} ;
render ( data ) ;
}
}ดีมาก:
เป็นการดีกว่าที่จะใช้รหัสรุ่นกะทัดรัด
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆กลับไปด้านบน
การทดสอบมีความสำคัญมากกว่าการจัดส่ง หากคุณไม่มีการทดสอบหรือจำนวนเงินที่ไม่เพียงพอทุกครั้งที่คุณจัดส่งรหัสคุณจะไม่แน่ใจว่าคุณไม่ได้ทำลายอะไรเลย การตัดสินใจเลือกสิ่งที่ถือเป็นจำนวนที่เพียงพอขึ้นอยู่กับทีมของคุณ แต่การได้รับความคุ้มครอง 100% (ข้อความและสาขาทั้งหมด) คือวิธีที่คุณบรรลุความมั่นใจสูงและความสงบของนักพัฒนา ซึ่งหมายความว่านอกเหนือจากการมีกรอบการทดสอบที่ยอดเยี่ยมคุณต้องใช้เครื่องมือครอบคลุมที่ดี
ไม่มีข้อแก้ตัวที่จะไม่เขียนการทดสอบ มีเฟรมเวิร์กการทดสอบ. NET ที่ดีมากมายดังนั้นค้นหาสิ่งที่ทีมของคุณชอบ เมื่อคุณพบหนึ่งที่ใช้ได้ผลกับทีมของคุณจากนั้นตั้งเป้าหมายที่จะเขียนการทดสอบสำหรับคุณสมบัติ/โมดูลใหม่ที่คุณแนะนำเสมอ หากวิธีการที่คุณต้องการคือการทดสอบการพัฒนา (TDD) นั่นเป็นสิ่งที่ยอดเยี่ยม แต่ประเด็นหลักคือการทำให้แน่ใจว่าคุณบรรลุเป้าหมายการครอบคลุมของคุณก่อนที่จะเปิดตัวคุณลักษณะใด ๆ
ตรวจสอบให้แน่ใจว่าการทดสอบของคุณเน้นเลเซอร์และไม่ทดสอบสิ่งที่ผิดพลาด (ไม่เกี่ยวข้อง) บังคับให้พ่อ AAA ใช้เพื่อให้รหัสของคุณสะอาดและอ่านง่ายขึ้น
แย่:
public class MakeDotNetGreatAgainTests
{
[ Fact ]
public void HandleDateBoundaries ( )
{
var date = new MyDateTime ( "1/1/2015" ) ;
date . AddDays ( 30 ) ;
Assert . Equal ( "1/31/2015" , date ) ;
date = new MyDateTime ( "2/1/2016" ) ;
date . AddDays ( 28 ) ;
Assert . Equal ( "02/29/2016" , date ) ;
date = new MyDateTime ( "2/1/2015" ) ;
date . AddDays ( 28 ) ;
Assert . Equal ( "03/01/2015" , date ) ;
}
}ดี:
public class MakeDotNetGreatAgainTests
{
[ Fact ]
public void Handle30DayMonths ( )
{
// Arrange
var date = new MyDateTime ( "1/1/2015" ) ;
// Act
date . AddDays ( 30 ) ;
// Assert
Assert . Equal ( "1/31/2015" , date ) ;
}
[ Fact ]
public void HandleLeapYear ( )
{
// Arrange
var date = new MyDateTime ( "2/1/2016" ) ;
// Act
date . AddDays ( 28 ) ;
// Assert
Assert . Equal ( "02/29/2016" , date ) ;
}
[ Fact ]
public void HandleNonLeapYear ( )
{
// Arrange
var date = new MyDateTime ( "2/1/2015" ) ;
// Act
date . AddDays ( 28 ) ;
// Assert
Assert . Equal ( "03/01/2015" , date ) ;
}
}soure https://www.codingblocks.net/podcast/how-to-write-amazing-unit-tests
⬆กลับไปด้านบน
สรุปแนวทางการเขียนโปรแกรมแบบอะซิงโครนัส
| ชื่อ | คำอธิบาย | ข้อยกเว้น |
|---|---|---|
| หลีกเลี่ยง Async เป็นโมฆะ | ชอบวิธีการงาน async มากกว่าวิธี Async Void | ตัวจัดการเหตุการณ์ |
| async ตลอดทาง | อย่าผสมการบล็อกและรหัส async | วิธีการหลักของคอนโซล (C# <= 7.0) |
| กำหนดค่าบริบท | ใช้ ConfigureAwait(false) เมื่อคุณทำได้ | วิธีการที่ต้องการบริบท |
วิธีการทำสิ่งต่างๆ
| เพื่อทำสิ่งนี้ ... | แทนที่จะเป็นเช่นนี้ ... | ใช้สิ่งนี้ |
|---|---|---|
| ดึงผลลัพธ์ของงานพื้นหลัง | Task.Wait or Task.Result | await |
| รอให้งานใด ๆ เสร็จสมบูรณ์ | Task.WaitAny | await Task.WhenAny |
| ดึงผลลัพธ์ของงานหลายงาน | Task.WaitAll | await Task.WhenAll |
| รอช่วงเวลาหนึ่ง | Thread.Sleep | await Task.Delay |
แนวปฏิบัติที่ดีที่สุด
Async/Await เป็นงานที่ดีที่สุดสำหรับงาน IO ที่ถูกผูกไว้ (การสื่อสารเครือข่าย, การสื่อสารฐานข้อมูล, คำขอ HTTP ฯลฯ ) แต่ก็ไม่ดีที่จะใช้กับงานที่ถูกผูกไว้ในการคำนวณ เพราะมันจะปล่อยเธรดการถือครองไปยังพูลเธรดและ CPU/คอร์ที่มีอยู่จะไม่เกี่ยวข้องกับการประมวลผลงานเหล่านั้น ดังนั้นเราควรหลีกเลี่ยงการใช้ Async/รองานที่ถูกผูกไว้
สำหรับการจัดการกับงานที่ถูกผูกไว้ในการคำนวณชอบที่จะใช้ Task.Factory.CreateNew ด้วย TaskCreationOptions เป็น LongRunning มันจะเริ่มเธรดพื้นหลังใหม่เพื่อประมวลผลงานที่ถูกผูกไว้อย่างหนักโดยไม่ปล่อยกลับไปที่พูลเธรดจนกว่างานจะเสร็จสิ้น
รู้จักเครื่องมือของคุณ
มีหลายสิ่งที่ต้องเรียนรู้เกี่ยวกับ Async และรอคอยและเป็นเรื่องธรรมดาที่จะได้รับความสับสนเล็กน้อย นี่คือการอ้างอิงอย่างรวดเร็วของการแก้ไขปัญหาที่พบบ่อย
การแก้ปัญหา Async ทั่วไป
| ปัญหา | สารละลาย |
|---|---|
| สร้างงานเพื่อเรียกใช้รหัส | Task.Run หรือ TaskFactory.StartNew (ไม่ใช่ตัวสร้าง Task หรือ Task.Start ) |
| สร้าง wrapper งานสำหรับการดำเนินการหรือกิจกรรม | TaskFactory.FromAsync หรือ TaskCompletionSource<T> |
| สนับสนุนการยกเลิก | CancellationTokenSource และ CancellationToken |
| รายงานความคืบหน้า | IProgress<T> และ Progress<T> |
| จัดการกระแสข้อมูล | TPL dataflow หรือส่วนขยายปฏิกิริยา |
| ซิงโครไนซ์การเข้าถึงทรัพยากรที่ใช้ร่วมกัน | SemaphoreSlim |
| การเริ่มต้นใช้ทรัพยากรแบบอะซิงโครนัส | AsyncLazy<T> |
| โครงสร้างผู้ผลิต/ผู้บริโภคที่พร้อม Async | tpl dataflow หรือ AsyncCollection<T> |
อ่านเอกสารรูปแบบแบบอะซิงโครนัส (TAP) ตามงาน มันเขียนได้ดีมากและรวมถึงคำแนะนำเกี่ยวกับการออกแบบ API และการใช้ async/รออย่างเหมาะสม (รวมถึงการยกเลิกและการรายงานความคืบหน้า)
มีเทคนิคการรอคอยที่เป็นมิตรใหม่มากมายที่ควรใช้แทนเทคนิคการบล็อกแบบเก่า หากคุณมีตัวอย่างเก่า ๆ เหล่านี้ในรหัส Async ใหม่ของคุณคุณกำลังทำผิด (TM):
| เก่า | ใหม่ | คำอธิบาย |
|---|---|---|
task.Wait | await task | รอ/รอให้งานเสร็จสมบูรณ์ |
task.Result | await task | รับผลของงานที่เสร็จสมบูรณ์ |
Task.WaitAny | await Task.WhenAny | รอ/รอหนึ่งในคอลเลกชันของงานที่จะทำให้เสร็จสมบูรณ์ |
Task.WaitAll | await Task.WhenAll | รอ/รอให้ทุกงานของงานเสร็จสมบูรณ์ |
Thread.Sleep | await Task.Delay | รอ/รอเป็นระยะเวลาหนึ่ง |
ตัวสร้าง Task | Task.Run หรือ TaskFactory.StartNew | สร้างงานตามรหัส |
แหล่งที่มา https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
⬆กลับไปด้านบน
ข้อผิดพลาดที่ถูกโยนทิ้งเป็นสิ่งที่ดี! พวกเขาหมายถึงรันไทม์ที่ระบุได้สำเร็จเมื่อบางสิ่งในโปรแกรมของคุณผิดพลาดและแจ้งให้คุณทราบโดยการหยุดการทำงานของฟังก์ชั่นบนสแต็กปัจจุบันฆ่ากระบวนการ (ใน. NET/.NET Core) และแจ้งให้คุณทราบในคอนโซลด้วยร่องรอยสแต็ก
หากคุณต้องการโยนข้อยกเว้นอีกครั้งหลังจากจับมันให้ใช้เพียงแค่ 'โยน' โดยใช้สิ่งนี้คุณจะบันทึกการติดตามสแต็ก แต่ในตัวเลือกที่ไม่ดีด้านล่างคุณจะสูญเสียการติดตามสแต็ก
แย่:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ex ;
}ดี:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ;
}⬆กลับไปด้านบน
การไม่ทำอะไรเลยกับข้อผิดพลาดที่ถูกจับไม่ได้ทำให้คุณสามารถแก้ไขหรือตอบสนองต่อข้อผิดพลาดดังกล่าวได้ การขว้างข้อผิดพลาดนั้นไม่ดีขึ้นมากนักบ่อยครั้งที่มันจะหายไปในทะเลของสิ่งที่พิมพ์ลงในคอนโซล หากคุณห่อรหัสบิตใด ๆ ใน try/catch หมายความว่าคุณคิดว่าข้อผิดพลาดอาจเกิดขึ้นที่นั่นและดังนั้นคุณควรมีแผนหรือสร้างเส้นทางรหัสสำหรับเมื่อมันเกิดขึ้น
แย่:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}ดี:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆กลับไปด้านบน
หากคุณต้องการดำเนินการตามประเภทของข้อยกเว้นคุณควรใช้หลายบล็อกการจับสำหรับการจัดการข้อยกเว้น
แย่:
try
{
// Do something..
}
catch ( Exception ex )
{
if ( ex is TaskCanceledException )
{
// Take action for TaskCanceledException
}
else if ( ex is TaskSchedulerException )
{
// Take action for TaskSchedulerException
}
}ดี:
try
{
// Do something..
}
catch ( TaskCanceledException ex )
{
// Take action for TaskCanceledException
}
catch ( TaskSchedulerException ex )
{
// Take action for TaskSchedulerException
}⬆กลับไปด้านบน
C# อนุญาตให้มีข้อยกเว้นใหม่ในบล็อกจับโดยใช้คำ throw มันเป็นวิธีปฏิบัติที่ไม่ดีที่จะโยนข้อยกเว้นที่ถูกจับโดยใช้ throw e; - คำสั่งนี้รีเซ็ตการติดตามสแต็ก ใช้ throw; - สิ่งนี้จะช่วยให้สแต็คติดตามและให้ข้อมูลเชิงลึกที่ลึกซึ้งยิ่งขึ้นเกี่ยวกับข้อยกเว้น อีกทางเลือกหนึ่งคือการใช้ข้อยกเว้นที่กำหนดเอง เพียงแค่ยกตัวอย่างข้อยกเว้นใหม่และตั้งค่าคุณสมบัติข้อยกเว้นภายในเป็นข้อยกเว้นที่จับได้ด้วยการโยน new CustomException("some info", e); - การเพิ่มข้อมูลลงในข้อยกเว้นเป็นวิธีปฏิบัติที่ดีเพราะจะช่วยในการดีบัก อย่างไรก็ตามหากวัตถุประสงค์คือการบันทึกข้อยกเว้นให้ใช้ throw; เพื่อส่งเจ้าชู้ไปยังผู้โทร
แย่:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
logger . LogInfo ( ex ) ;
throw ex ;
}ดี:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw ;
}ดี:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw new CustomException ( error ) ;
}⬆กลับไปด้านบน
แย่:
มีรูปแบบการจัดรูปแบบรหัสมากมายในโครงการ ตัวอย่างเช่นสไตล์การเยื้องคือ space และ tab ผสมในโครงการ
ดี:
กำหนดและรักษารูปแบบรหัสที่สอดคล้องกันใน codebase ของคุณด้วยการใช้ไฟล์ .editorconfig
root = true
[ * ]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf - 8
trim_trailing_whitespace = true
insert_final_newline = true
# C# files
[ * . cs ]
indent_size = 4
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_within_query_expression_clauses = true
# Code files
[ * . { cs , csx , vb , vbx } ]
indent_size = 4
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# avoid this . unless absolutely necessary
dotnet_style_qualification_for_field = false : suggestion
dotnet_style_qualification_for_property = false : suggestion
dotnet_style_qualification_for_method = false : suggestion
dotnet_style_qualification_for_event = false : suggestion
# only use var when it's obvious what the variable type is
# csharp_style_var_for_built_in_types = false : none
# csharp_style_var_when_type_is_apparent = false : none
# csharp_style_var_elsewhere = false : suggestion
# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true : suggestion
dotnet_style_predefined_type_for_member_access = true : suggestion
# name all constant fields using PascalCase
dotnet_naming_rule . constant_fields_should_be_pascal_case . severity = suggestion
dotnet_naming_rule . constant_fields_should_be_pascal_case . symbols = constant_fields
dotnet_naming_rule . constant_fields_should_be_pascal_case . style = pascal_case_style
dotnet_naming_symbols . constant_fields . applicable_kinds = field
dotnet_naming_symbols . constant_fields . required_modifiers = const
dotnet_naming_style . pascal_case_style . capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule . static_fields_should_have_prefix . severity = suggestion
dotnet_naming_rule . static_fields_should_have_prefix . symbols = static_fields
dotnet_naming_rule . static_fields_should_have_prefix . style = static_prefix_style
dotnet_naming_symbols . static_fields . applicable_kinds = field
dotnet_naming_symbols . static_fields . required_modifiers = static
dotnet_naming_style . static_prefix_style . required_prefix = s_
dotnet_naming_style . static_prefix_style . capitalization = camel_case
# i nternal and private fields should be _camelCase
dotnet_naming_rule . camel_case_for_private_internal_fields . severity = suggestion
dotnet_naming_rule . camel_case_for_private_internal_fields . symbols = private_internal_fields
dotnet_naming_rule . camel_case_for_private_internal_fields . style = camel_case_underscore_style
dotnet_naming_symbols . private_internal_fields . applicable_kinds = field
dotnet_naming_symbols . private_internal_fields . applicable_accessibilities = private , internal
dotnet_naming_style . camel_case_underscore_style . required_prefix = _
dotnet_naming_style . camel_case_underscore_style . capitalization = camel_case
# Code style defaults
dotnet_sort_system_directives_first = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
# Expression - level preferences
dotnet_style_object_initializer = true : suggestion
dotnet_style_collection_initializer = true : suggestion
dotnet_style_explicit_tuple_names = true : suggestion
dotnet_style_coalesce_expression = true : suggestion
dotnet_style_null_propagation = true : suggestion
# Expression - bodied members
csharp_style_expression_bodied_methods = false : none
csharp_style_expression_bodied_constructors = false : none
csharp_style_expression_bodied_operators = false : none
csharp_style_expression_bodied_properties = true : none
csharp_style_expression_bodied_indexers = true : none
csharp_style_expression_bodied_accessors = true : none
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true : suggestion
csharp_style_pattern_matching_over_as_with_null_check = true : suggestion
csharp_style_inlined_variable_declaration = true : suggestion
# Null checking preferences
csharp_style_throw_expression = true : suggestion
csharp_style_conditional_delegate_call = true : suggestion
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
[ * . { asm , inc } ]
indent_size = 8
# Xml project files
[ * . { csproj , vcxproj , vcxproj . filters , proj , nativeproj , locproj } ]
indent_size = 2
# Xml config files
[ * . { props , targets , config , nuspec } ]
indent_size = 2
[ CMakeLists . txt ]
indent_size = 2
[ * . cmd ]
indent_size = 2⬆กลับไปด้านบน
พวกเขามักจะเพิ่มเสียงรบกวน ให้ฟังก์ชั่นและชื่อตัวแปรพร้อมกับการเยื้องที่เหมาะสมและการจัดรูปแบบให้โครงสร้างภาพกับรหัสของคุณ
แย่:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;แย่:
#region Scope Model Instantiation
var model = {
menu : 'foo' ,
nav : 'bar'
} ;
#endregion
#region Action setup
void Actions ( ) {
// ...
} ;
#endregionดี:
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
void Actions ( )
{
// ...
} ;⬆กลับไปด้านบน
การควบคุมเวอร์ชันมีอยู่ด้วยเหตุผล ทิ้งรหัสเก่าไว้ในประวัติศาสตร์ของคุณ
แย่:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();ดี:
doStuff ( ) ;⬆กลับไปด้านบน
จำไว้ว่าใช้การควบคุมเวอร์ชัน! ไม่จำเป็นต้องใช้รหัสตายรหัสแสดงความคิดเห็นและโดยเฉพาะอย่างยิ่งความคิดเห็นในวารสาร ใช้ git log เพื่อรับประวัติ!
แย่:
/**
* 2018-12-20: Removed monads, didn't understand them (RM)
* 2017-10-01: Improved using special monads (JP)
* 2016-02-03: Removed type-checking (LI)
* 2015-03-14: Added combine with type-checking (JR)
*/
public int Combine ( int a , int b )
{
return a + b ;
}ดี:
public int Combine ( int a , int b )
{
return a + b ;
}⬆กลับไปด้านบน
ความคิดเห็นเป็นคำขอโทษไม่ใช่ข้อกำหนด รหัสที่ดี ส่วนใหญ่ เป็นเอกสารของตัวเอง
แย่:
public int HashIt ( string data )
{
// The hash
var hash = 0 ;
// Length of string
var length = data . length ;
// Loop through every character in data
for ( var i = 0 ; i < length ; i ++ )
{
// Get character code.
const char = data . charCodeAt ( i ) ;
// Make the hash
hash = ( ( hash << 5 ) - hash ) + char ;
// Convert to 32-bit integer
hash &= hash ;
}
}ดีกว่า แต่ยังไม่ดี:
public int HashIt ( string data )
{
var hash = 0 ;
var length = data . length ;
for ( var i = 0 ; i < length ; i ++ )
{
const char = data . charCodeAt ( i ) ;
hash = ( ( hash << 5 ) - hash ) + char ;
// Convert to 32-bit integer
hash &= hash ;
}
} หากความคิดเห็นอธิบายว่ารหัสกำลังทำอะไรอาจเป็นความคิดเห็นที่ไร้ประโยชน์และสามารถนำไปใช้กับตัวแปรหรือฟังก์ชั่นที่มีชื่อดี ความคิดเห็นในรหัสก่อนหน้าสามารถถูกแทนที่ด้วยฟังก์ชั่นชื่อ ConvertTo32bitInt ดังนั้นความคิดเห็นนี้ยังคงไร้ประโยชน์ อย่างไรก็ตามมันยากที่จะแสดงด้วยรหัสว่าทำไมนักพัฒนาจึงเลือกอัลกอริทึม HASH DJB2 แทนฟังก์ชั่น SHA-1 หรือฟังก์ชั่นแฮชอื่น ๆ ในกรณีนั้นความคิดเห็นเป็นที่ยอมรับ
ดี:
public int Hash ( string data )
{
var hash = 0 ;
var length = data . length ;
for ( var i = 0 ; i < length ; i ++ )
{
var character = data [ i ] ;
// use of djb2 hash algorithm as it has a good compromise
// between speed and low collision with a very simple implementation
hash = ( ( hash << 5 ) - hash ) + character ;
hash = ConvertTo32BitInt ( hash ) ;
}
return hash ;
}
private int ConvertTo32BitInt ( int value )
{
return value & value ;
}⬆กลับไปด้านบน
ขอขอบคุณทุกคนที่มีส่วนร่วมในโครงการ clean-code-dotnet
รักงานของเราและช่วยเราทำกิจกรรมต่อไป? [เป็นผู้สนับสนุน]
เป็นสปอนเซอร์และรับโลโก้ของคุณใน readme ของเราบน GitHub พร้อมลิงค์ไปยังเว็บไซต์ของคุณ [เป็นสปอนเซอร์]
ตามขอบเขตที่เป็นไปได้ภายใต้กฎหมาย Thangchung ได้สละสิทธิ์ลิขสิทธิ์และสิทธิที่เกี่ยวข้องหรือใกล้เคียงกับงานนี้ทั้งหมด