如果您喜歡clean-code-dotnet項目,或者如果對您有所幫助,請為此存儲庫提供一顆星。這不僅將有助於加強我們的.NET社區,而且還可以提高世界各地.NET開發人員的清潔代碼技能。非常感謝 ?
查看我的博客或在Twitter上打招呼!
羅伯特·馬丁( Robert C.這不是樣式指南。它是在.NET/.NET Core中生產可讀,可重複使用和可重構軟件的指南。
並非必須嚴格遵循此處的所有原則,甚至更少會被普遍同意。這些是準則,僅此而已,但是它們是在多年的集體經驗中由清潔代碼的集體經驗編纂的。
靈感來自清潔代碼javaScript和Clean-ode-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 ;匈牙利符號也不應在參數術中使用。
壞的:
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 ; }
}⬆回到頂部
使用駱駝箱表示法進行可變和方法參數。
壞的:
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 ) ;
}⬆回到頂部
魔術字符串是直接在應用程序代碼中指定的字符串值,對應用程序的行為產生影響。通常,這樣的字符串最終會在系統中重複,並且由於無法使用重構工具自動更新它們,因此當對某些字符串進行更改而不是其他字符串進行更改時,它們成為常見的錯誤來源。
壞的
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 ) ;
}好的:
通過命名子圖案降低對正則對等級的依賴。
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沒有陳述,我應該怎麼做什麼?”答案是您可以使用多態性在許多情況下完成相同的任務。第二個問題通常是:“那太好了,但是我為什麼要這樣做呢?”答案是我們學到的以前的干淨代碼概念:函數只能做一件事。當您具有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 Hevery關於問題的根源也有很好的想法。
壞的:
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 )
{
// ...
}⬆回到頂部
這是迄今為止軟件工程中最重要的規則。當功能做不止一件事時,它們很難構成,測試和理由。當您只能將功能隔離為一個操作時,它們可以輕鬆進行重構,並且您的代碼將讀取更清潔。如果您除了本指南以外什麼都不帶走,那麼您將領先於許多開發人員。
壞的:
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...
}
}
}⬆回到頂部
如果函數調用另一個函數,請在源文件中垂直垂直關閉這些函數。理想情況下,將呼叫者保持在卡利上方。我們傾向於像報紙一樣從上到下閱讀代碼。因此,使您的代碼讀取方式。
壞的:
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 ( ) )
{
// ...
}⬆回到頂部
死代碼與重複代碼一樣糟糕。沒有理由將其保存在您的代碼庫中。如果沒有被稱為,請擺脫它!如果您仍然需要它,它在您的版本歷史記錄中仍然是安全的。
壞的:
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 ( ) ;
}⬆回到頂部
正如四個團伙在設計模式中著名的那樣,您應該更喜歡構圖而不是繼承。有很多充分的理由使用繼承和許多充分的理由使用構圖。
這一格言的要點是,如果您的思想本能地進行繼承,請嘗試思考構圖是否可以更好地建模您的問題。在某些情況下可以。
然後,您可能想知道:“我什麼時候應該使用繼承?”這取決於您手頭的問題,但這是繼承何時比組成更有意義的列表:
壞的:
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命名的前五個原則提出的助記符首字母縮寫,該原則是面向對象的編程和設計的五個基本原則。
如乾淨的代碼中所述,“不應該有一個班級更改的理由”。很容易用很多功能擠壓一堂課,例如,您只能在飛行中乘坐一個手提箱。這樣做的問題是,您的班級在概念上不會具有凝聚力,它將給它帶來許多改變的理由。最大程度地減少您需要更改課程的次數很重要。
這很重要,因為如果在一個類中過多功能並修改其中的一部分,那麼很難理解這將如何影響代碼庫中其他依賴模塊。
壞的:
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的子類型,則可以用類型s的對象替換T型的對象(即S類型S類型的對象可以代替T型的對象)而不會更改該程序的任何理想屬性(正確性,任務,執行等)。”這是一個更可怕的定義。
最好的解釋是,如果您有父母課程和子類,那麼基類和子類可以互換使用而不會獲得不正確的結果。這可能仍然令人困惑,所以讓我們來看看經典的Square Rectangle示例。從數學上講,一個正方形是矩形,但是如果您使用繼承使用“ 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核心框架,則您已經以依賴注入(DI)的形式看到了該原則的實現。雖然它們不是相同的概念,但DIP可以防止高級模塊知道其低級模塊的細節並設置它們。它可以通過DI實現這一目標。這樣一個巨大的好處是,它減少了模塊之間的耦合。耦合是一種非常糟糕的開發模式,因為它使您的代碼難以重構。
壞的:
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 ( ) ;
}
}
}⬆回到頂部
嘗試觀察乾燥原則。
盡力避免重複代碼。重複的代碼很糟糕,因為這意味著如果您需要更改某些邏輯,則有多個地方可以更改某些內容。
想像一下,如果您經營一家餐廳,並且可以跟踪庫存:所有西紅柿,洋蔥,大蒜,香料等。如果您有多個列表,則必須在其中搭配西紅柿一起食用一道菜時都必須進行更新。如果您只有一個列表,則只有一個可以更新的地方!
通常,您有重複的代碼,因為您有兩個或更多稍微不同的東西,它們有很多共同點,但是它們的差異迫使您擁有兩個或多個獨立的功能,可以做很多相同的事情。刪除重複代碼意味著創建一個可以使用一個功能/模塊/類來處理這組不同事物的抽象。
正確獲取抽象至關重要,這就是為什麼您應該遵循課程中規定的可靠原則的原因。不良的抽象可能比重複的代碼差,所以要小心!話雖如此,如果您可以做出一個良好的抽象,那就去做吧!不要重複自己,否則您會發現自己想更改一件事時會更新多個位置。
壞的:
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 ) ;
}
}非常好:
最好使用CODE的緊湊版本。
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆回到頂部
測試比運輸更重要。如果您沒有測試或數量不足,那麼每次發貨代碼時,您都不會確定自己沒有破壞任何東西。確定構成足夠數量的什麼取決於您的團隊,但是擁有100%的覆蓋範圍(所有聲明和分支機構)是您如何獲得很高的信心和開發人員的安心。這意味著,除了擁有出色的測試框架外,您還需要使用良好的覆蓋範圍工具。
沒有藉口不寫測試。有很多優質的.NET測試框架,因此請找到您的團隊更喜歡的測試框架。當您找到一個適合團隊工作的人時,請始終為您介紹的每個新功能/模塊編寫測試。如果您的首選方法是測試驅動的開發(TDD),那很棒,但是要點是要確保在啟動任何功能或重構現有功能之前要達到覆蓋目標。
確保您的測試專注於激光,並且不要測試雜交(無關)的事物,這會迫使AAA PATERN用來使您的代碼更加干淨和可讀。
壞的:
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-to-write-amazing-unit-tests
⬆回到頂部
異步編程指南的摘要
| 姓名 | 描述 | 例外 |
|---|---|---|
| 避免異步無效 | 優於異步任務方法而不是異步空隙方法 | 活動處理程序 |
| 一路異步 | 不要混合阻塞和異步代碼 | 控制台主方法(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 |
最佳實踐
異步/等待是IO綁定任務的最佳選擇(網絡通信,數據庫通信,HTTP請求等),但應用於計算界限任務(在巨大列表上,呈現ungge image等)不好。因為它將將固定線程釋放到線程池,而可用的CPU/內核將不涉及處理這些任務。因此,我們應該避免使用異步/等待構成任務。
對於處理計算綁定的任務,更喜歡使用Task.Factory.CreateNew TaskCreationOptions是LongRunning 。它將啟動一個新的背景線程,以處理一個重型計算綁定任務,而無需將其釋放迴線池,直到完成任務為止。
知道您的工具
有很多關於異步和等待的需要學習的東西,很自然會有一些迷失方向。這是對常見問題的解決方案的快速參考。
常見異步問題的解決方案
| 問題 | 解決方案 |
|---|---|
| 創建一個任務以執行代碼 | Task.Run或TaskFactory.StartNew (不是Task構建器或Task.Start ) |
| 為操作或事件創建任務包裝器 | TaskFactory.FromAsync或TaskCompletionSource<T> |
| 支持取消 | CancellationTokenSource和CancellationToken |
| 報告進度 | IProgress<T>和Progress<T> |
| 處理數據流 | TPL數據流或反應性擴展 |
| 同步訪問共享資源 | SemaphoreSlim |
| 異步初始化資源 | AsyncLazy<T> |
| 異步準備的生產商/消費者結構 | tpl dataflow或AsyncCollection<T> |
閱讀基於任務的異步模式(TAP)文檔。它寫得非常好,包括有關API設計的指南和適當使用異步/等待(包括取消和進度報告)。
應該使用許多新的等待友好的技術,而不是舊的阻止技術。如果您在新的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 。
好的:
使用.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的函數替換,因此此評論仍然沒有用。但是,很難通過代碼來表達為什麼開發人員選擇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項目做出貢獻的人
熱愛我們的工作並幫助我們繼續我們的活動? [成為支持者]
成為贊助商,並在Github上的讀書中獲取您的徽標,並帶有指向您網站的鏈接。 [成為贊助商]
根據法律的可能,Thangchung放棄了這項工作的所有版權以及相關或鄰近的權利。