Wenn Ihnen clean-code-dotnet -Projekt gefallen hat oder wenn es Ihnen geholfen hat, geben Sie bitte einen Stern für dieses Repository. Dies wird nicht nur dazu beitragen, unsere .NET -Community zu stärken, sondern auch die Fähigkeiten über den sauberen Code für .NET -Entwickler in der ganzen Welt zu verbessern. Vielen Dank ?
Schauen Sie sich meinen Blog an oder sagen Sie Hallo auf Twitter!
Software -Engineering -Prinzipien aus Robert C. Martins Buch Clean Code , angepasst für .NET/.NET CORE. Dies ist kein Stilhandbuch. Es ist ein Leitfaden für die Erzeugung lesbarer, wiederverwendbarer und refaktivierter Software in .NET/.NET CORE.
Nicht jedes Prinzip hierin muss streng befolgt werden, und noch weniger werden allgemein vereinbart. Dies sind Richtlinien und nichts weiter, aber sie sind diejenigen, die über viele Jahre kollektiver Erfahrung durch die Autoren von Clean Code kodifiziert wurden.
Inspiriert von Listen mit sauberer Code-JavaScript und sauberer Code-Php.
Schlecht:
int d ;Gut:
int daySinceModification ;⬆ Zurück nach oben
Nennen Sie die Variable, um zu reflektieren, wofür sie verwendet wird.
Schlecht:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;Gut:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ Zurück nach oben
Ungarische Notation berücksichtigt den Typ, der bereits in der Erklärung vorhanden ist. Dies ist sinnlos, da moderne IDEs den Typ identifizieren.
Schlecht:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;Gut:
int counter ;
string fullName ;
DateTime modifiedDate ;Ungarische Notation sollte auch in Paramatern nicht verwendet werden.
Schlecht:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}Gut:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ Zurück nach oben
Die Kapitalisierung sagt Ihnen viel über Ihre Variablen, Funktionen usw. aus. Diese Regeln sind subjektiv, damit Ihr Team alles auswählen kann, was es will. Der Punkt ist, egal was Sie alle wählen, nur konsequent.
Schlecht:
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 { }Gut:
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 { }⬆ Zurück nach oben
Es wird einige Zeit dauern, um die Bedeutung der Variablen und Funktionen zu untersuchen, wenn sie nicht ausgesprochen sind.
Schlecht:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}Gut:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆ Zurück nach oben
Verwenden Sie die Kamelcase -Notation für Variable- und Methodenparamater.
Schlecht:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}Gut:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ Zurück nach oben
Personen, die Ihren Code lesen, sind auch Programmierer. Wenn Sie die Dinge richtig benennen, hilft es jedem, auf derselben Seite zu stehen. Wir wollen keine Zeit nehmen, um jedem zu erklären, wofür eine Variable oder Funktion ist.
Gut
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 ( ) ;
}⬆ Zurück nach oben
Zu viele, wenn sonst Anweisungen den Code schwierig machen können. Explizit ist besser als implizit .
Schlecht:
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 ;
}
}Gut:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}Schlecht:
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" ) ;
}
}Gut:
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 ) ;
}⬆ Zurück nach oben
Erzwingen Sie den Leser Ihres Codes nicht, um zu übersetzen, was die Variable bedeutet. Explizit ist besser als implizit .
Schlecht:
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 ) ;
}Gut:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆ Zurück nach oben
Magische Zeichenfolgen sind Stringwerte, die direkt im Anwendungscode angegeben sind, die sich auf das Verhalten der Anwendung auswirken. Häufig werden solche Saiten innerhalb des Systems dupliziert, und da sie nicht automatisch mithilfe von Refactoring -Tools aktualisiert werden können, werden sie zu einer gängigen Fehlerquelle, wenn Änderungen an einigen Saiten, aber nicht an anderen vorgenommen werden.
Schlecht
if ( userRole == "Admin" )
{
// logic in here
}Gut
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}Mit dieser Weise müssen wir uns nur in zentralisierter Stelle ändern, und andere werden ihn anpassen.
⬆ Zurück nach oben
Wenn Ihr Klasse/Objektname Ihnen etwas sagt, wiederholen Sie dies nicht in Ihrem variablen Namen.
Schlecht:
public class Car
{
public string CarMake { get ; set ; }
public string CarModel { get ; set ; }
public string CarColor { get ; set ; }
//...
}Gut:
public class Car
{
public string Make { get ; set ; }
public string Model { get ; set ; }
public string Color { get ; set ; }
//...
}⬆ Zurück nach oben
Schlecht:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;Gut:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆ Zurück nach oben
Schlecht:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;Gut:
GetUser ( ) ;⬆ Zurück nach oben
Wir werden mehr Code lesen, als wir jemals schreiben werden. Es ist wichtig, dass der Code, den wir schreiben, lesbar und durchsuchbar ist. Indem wir keine Variablen nennen, die für das Verständnis unseres Programms sinnvoll sind, haben wir unsere Leser verletzt. Machen Sie Ihre Namen durchsuchbar.
Schlecht:
// 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 ( ) ) ;Gut:
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 ( ) ) ;⬆ Zurück nach oben
Schlecht:
var data = new { Name = "John" , Age = 42 , PersonAccess = 4 } ;
// What the heck is 4 for?
if ( data . PersonAccess == 4 )
{
// do edit ...
}Gut:
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 ...
}⬆ Zurück nach oben
Schlecht:
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 ) ;
}Gut:
Verringern Sie die Abhängigkeit von Regex durch Benennung von Subpattern.
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 ) ;
}⬆ Zurück nach oben
Nicht gut:
Dies ist nicht gut, weil breweryName NULL sein kann.
Diese Meinung ist verständlicher als die vorherige Version, steuert jedoch den Wert der Variablen besser.
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}Gut:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆ Zurück nach oben
Eine Funktion erzeugt einen Nebeneffekt, wenn sie etwas anderes als einen Wert einnimmt und einen anderen Wert oder einen anderen Wert zurückgibt. Ein Nebeneffekt könnte darin bestehen, eine Datei zu schreiben, eine globale Variable zu ändern oder versehentlich Ihr gesamtes Geld an einen Fremden zu verkaufen.
Jetzt müssen Sie gelegentlich Nebenwirkungen in einem Programm haben. Wie beim vorherigen Beispiel müssen Sie möglicherweise in eine Datei schreiben. Was Sie tun möchten, ist zu zentralisieren, wo Sie dies tun. Sie haben keine mehrere Funktionen und Klassen, die in eine bestimmte Datei schreiben. Haben Sie einen Service, der es tut. Eins und nur eins.
Der Hauptpunkt besteht darin, häufige Fallstricke wie den Austauschzustand zwischen Objekten ohne Struktur zu vermeiden, mithilfe veränderlicher Datentypen, auf die durch irgendetwas geschrieben werden kann, und nicht zentralisiert, wo Ihre Nebenwirkungen auftreten. Wenn Sie dies tun können, werden Sie glücklicher als die überwiegende Mehrheit der anderen Programmierer.
Schlecht:
// 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 McDermottGut:
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⬆ Zurück nach oben
Schlecht:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}Gut:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ Zurück nach oben
Dies scheint eine unmögliche Aufgabe zu sein. Als ich das zum ersten Mal hörte, sagen die meisten Leute: "Wie soll ich etwas ohne eine if -Aussage machen?" Die Antwort ist, dass Sie in vielen Fällen den Polymorphismus verwenden können, um dieselbe Aufgabe zu erreichen. Die zweite Frage lautet normalerweise: "Nun, das ist großartig, aber warum sollte ich das tun wollen?" Die Antwort ist ein früheres Clear -Code -Konzept, das wir gelernt haben: Eine Funktion sollte nur eine Sache erledigen. Wenn Sie Klassen und Funktionen haben, die if Anweisungen verfügen, sagen Sie Ihrem Benutzer, dass Ihre Funktion mehr als eine Sache macht. Denken Sie daran, machen Sie einfach eine Sache.
Schlecht:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}Gut:
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 ( ) ;
}
}⬆ Zurück nach oben
Schlecht:
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" ) ) ;
}
}Gut:
public Path TravelToTexas ( Traveler vehicle )
{
vehicle . TravelTo ( new Location ( "texas" ) ) ;
}oder
// 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" ) ) ;
}
}⬆ Zurück nach oben
Schlecht:
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 ;
}Gut:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆ Zurück nach oben
Ein Flag zeigt an, dass die Methode mehr als eine Verantwortung hat. Es ist am besten, wenn die Methode nur eine einzige Verantwortung hat. Teilen Sie die Methode in zwei Teilen, wenn ein Boolescher Parameter der Methode mehrere Verantwortlichkeiten hinzufügt.
Schlecht:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}Gut:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆ Zurück nach oben
Die Umweltverschmutzung von Global ist in vielen Sprachen eine schlechte Praxis, da Sie mit einer anderen Bibliothek zusammenbrechen können und der Benutzer Ihrer API nicht wärmer wäre, bis sie eine Ausnahme in der Produktion erhalten. Denken wir über ein Beispiel nach: Was ist, wenn Sie ein Konfigurationsarray haben möchten? Sie könnten globale Funktionen wie Config() schreiben, aber es könnte mit einer anderen Bibliothek zusammenbrechen, die versuchte, dasselbe zu tun.
Schlecht:
public Dictionary < string , string > Config ( )
{
return new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ;
}Gut:
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 ;
}
} Laden Sie die Konfiguration und erstellen Sie die Instanz der Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; Und jetzt müssen Sie eine Instanz der Configuration in Ihrer Anwendung verwenden.
⬆ Zurück nach oben
Singleton ist ein Anti-Muster. Von Brian Button umschrieben:
Es gibt auch sehr gute Gedanken von Misko Misvery über die Wurzel des Problems.
Schlecht:
class DBConnection
{
private static DBConnection _instance ;
private DBConnection ( )
{
// ...
}
public static GetInstance ( )
{
if ( _instance == null )
{
_instance = new DBConnection ( ) ;
}
return _instance ;
}
// ...
}
var singleton = DBConnection . GetInstance ( ) ;Gut:
class DBConnection
{
public DBConnection ( IOptions < DbConnectionOption > options )
{
// ...
}
// ...
} Erstellen Sie eine Instanz der DBConnection -Klasse und konfigurieren Sie sie mit Optionsmuster.
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; Und jetzt müssen Sie eine Instanz der DBConnection in Ihrer Anwendung verwenden.
⬆ Zurück nach oben
Die Begrenzung der Funktionsparameter ist unglaublich wichtig, da das Testen Ihrer Funktion erleichtert wird. Mehr als drei führt zu einer kombinatorischen Explosion, bei der Sie mit jedem separaten Argument Tonnen verschiedener Fälle testen müssen.
Null Argumente sind der ideale Fall. Ein oder zwei Argumente sind in Ordnung und drei sollten vermieden werden. Alles mehr als das sollte konsolidiert werden. Wenn Sie mehr als zwei Argumente haben, versucht Ihre Funktion zu viel zu tun. In Fällen, in denen dies nicht der Fall ist, reicht ein Objekt auf höherer Ebene als Argument aus.
Schlecht:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}Gut:
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 )
{
// ...
}⬆ Zurück nach oben
Dies ist bei weitem die wichtigste Regel im Software -Engineering. Wenn Funktionen mehr als eine Sache tun, sind sie schwerer zu komponieren, zu testen und zu vermitteln. Wenn Sie eine Funktion auf nur eine Aktion isolieren können, können sie leicht überarbeitet werden und Ihr Code wird viel sauberer gelesen. Wenn Sie diesen Leitfaden außer diesem nichts anderes wegnehmen, werden Sie vielen Entwicklern voraus sind.
Schlecht:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}Gut:
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" ) ;
}⬆ Zurück nach oben
Schlecht:
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 ( ) ;Gut:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆ Zurück nach oben
Noch nicht fertig
Wenn Sie mehr als eine Abstraktionsstufe haben, macht Ihre Funktion normalerweise zu viel. Das Aufteilen von Funktionen führt zu Wiederverwendbarkeit und einfacheren Tests.
Schlecht:
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...
}
}Schlecht auch:
Wir haben einen Teil der Funktionalität durchgeführt, aber die ParseBetterJSAlternative() -Funktion ist immer noch sehr komplex und nicht überprüfbar.
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...
}
}Gut:
Die beste Lösung ist, die Abhängigkeiten der ParseBetterJSAlternative() -Funktion auszuziehen.
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...
}
}
}⬆ Zurück nach oben
Wenn eine Funktion einen anderen aufruft, halten Sie diese Funktionen in der Quelldatei vertikal eng. Halten Sie den Anrufer im Idealfall direkt über dem Callee. Wir neigen dazu, Code von Top-to-Bottom zu lesen, wie eine Zeitung. Lassen Sie Ihren Code aus diesem Weise so lesen.
Schlecht:
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 ( ) ;Gut:
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 ( ) ;⬆ Zurück nach oben
Schlecht:
if ( article . state == "published" )
{
// ...
}Gut:
if ( article . IsPublished ( ) )
{
// ...
}⬆ Zurück nach oben
Dead Code ist genauso schlecht wie doppelter Code. Es gibt keinen Grund, es in Ihrer Codebasis zu behalten. Wenn es nicht angerufen wird, räumen Sie es los! Es ist immer noch in Ihrer Versionsgeschichte sicher, wenn Sie es noch brauchen.
Schlecht:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;Gut:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆ Zurück nach oben
In C# / vb.net können Sie public , protected und private Schlüsselwörter für Methoden festlegen. Mit der Verwendung können Sie die Änderung der Eigenschaften in einem Objekt steuern.
set einfach.Darüber hinaus ist dies Teil des offenen/geschlossenen Prinzips aus objektorientierten Entwurfsprinzipien.
Schlecht:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;Gut:
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 ;⬆ Zurück nach oben
Schlecht:
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 DoeGut:
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⬆ Zurück nach oben
Dieses Muster ist sehr nützlich und in vielen Bibliotheken häufig verwendet. Dadurch kann Ihr Code ausdrucksstark und weniger ausführlich sein. Verwenden Sie aus diesem Grund die Verkettung der Methode und sehen Sie sich an, wie sauber Ihr Code sein wird.
Gut:
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 ( ) ;
}⬆ Zurück nach oben
Wie die vierköpfige Bande in Designmustern berühmt gemacht hat, sollten Sie die Komposition gegenüber der Erbe bevorzugen, wo Sie können. Es gibt viele gute Gründe, die Erbschaft und viele gute Gründe für die Verwendung von Komposition zu verwenden.
Der Hauptpunkt für diese Maxim ist, dass, wenn Ihr Geist instinktiv zur Vererbung geht, zu denken, ob die Komposition Ihr Problem besser modellieren könnte. In einigen Fällen kann es.
Sie fragen sich dann vielleicht: "Wann sollte ich Erbschaft verwenden?" Es hängt von Ihrem vorliegenden Problem ab, aber dies ist eine anständige Liste, wann die Vererbung sinnvoller ist als die Komposition:
Schlecht:
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 )
{
// ...
}
// ...
}Gut:
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 ) ;
}
// ...
}⬆ Zurück nach oben
Solid ist das mnemonische Akronym, das von Michael Feathers für die ersten fünf Prinzipien, die von Robert Martin benannt wurden, eingeführt, was fünf Grundprinzipien der objektorientierten Programmierung und des Designs bedeutete.
Wie in Clean Code angegeben, "sollte es niemals mehr als einen Grund für eine Klasse geben, sich zu ändern". Es ist verlockend, eine Klasse mit viel Funktionalität zu packen, z. B. wenn Sie nur einen Koffer auf Ihrem Flug nehmen können. Das Problem dabei ist, dass Ihre Klasse nicht konzeptionell zusammenhängend ist und ihr viele Gründe zur Änderung gibt. Es ist wichtig, die Menge zu minimieren, in der Sie eine Klasse ändern müssen.
Es ist wichtig, denn wenn sich zu viel Funktionalität in einer Klasse befindet und Sie ein Stück davon ändern, kann es schwierig sein zu verstehen, wie sich dies auf andere abhängige Module in Ihrer Codebasis auswirkt.
Schlecht:
class UserSettings
{
private User User ;
public UserSettings ( User user )
{
User = user ;
}
public void ChangeSettings ( Settings settings )
{
if ( verifyCredentials ( ) )
{
// ...
}
}
private bool VerifyCredentials ( )
{
// ...
}
}Gut:
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 ( ) )
{
// ...
}
}
}⬆ Zurück nach oben
Wie von Bertrand Meyer angegeben, "sollten Softwareentitäten (Klassen, Module, Funktionen usw.) für die Erweiterung offen sein, aber für die Änderung geschlossen werden." Was bedeutet das aber? Dieses Prinzip besagt im Grunde, dass Sie Benutzern ermöglichen sollten, neue Funktionen hinzuzufügen, ohne den vorhandenen Code zu ändern.
Schlecht:
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
}
}Gut:
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 ) ;
}
}⬆ Zurück nach oben
Dies ist ein beängstigender Begriff für ein sehr einfaches Konzept. Es ist formell definiert als "Wenn s ein Subtyp von T ist, können Objekte vom Typ t durch Objekte vom Typ S (dh Objekte vom Typ S Objekte vom Typ t) ersetzt werden, ohne die wünschenswerten Eigenschaften dieses Programms (Korrektheit, Aufgabe, Aufgabe, ausgeführtes Aufgabe usw.) zu ändern." Das ist eine noch beängstigendere Definition.
Die beste Erklärung dafür ist, wenn Sie eine Elternklasse und eine untergeordnete Klasse haben, kann die Basisklasse und die untergeordnete Klasse austauschbar verwendet werden, ohne falsche Ergebnisse zu erzielen. Dies könnte immer noch verwirrend sein, also schauen wir uns das klassische Beispiel für quadratische Rekorde an. Mathematisch ist ein Quadrat ein Rechteck, aber wenn Sie es mit der Beziehung "is-a" durch Erbschaft modellieren, geraten Sie schnell in Schwierigkeiten.
Schlecht:
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 ) ;Gut:
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 ) ;⬆ Zurück nach oben
ISP gibt an, dass "Kunden nicht gezwungen sein sollten, sich auf Schnittstellen zu verlassen, die sie nicht verwenden".
Ein gutes Beispiel, um dies zu betrachten, zeigt dieses Prinzip für Klassen, die große Einstellungsobjekte erfordern. Es ist vorteilhaft, nicht alle Einstellungen zu erfordern, da Kunden keine großen Optionen einrichten müssen, da sie nicht alle Einstellungen benötigen. Wenn sie optional sind, verhindern Sie eine "Fettschnittstelle".
Schlecht:
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
}
}Gut:
Nicht jeder Arbeitnehmer ist ein Mitarbeiter, aber jeder Mitarbeiter ist Arbeitnehmer.
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
}
}⬆ Zurück nach oben
Dieses Prinzip besagt zwei wesentliche Dinge:
Dies kann zunächst schwer zu verstehen sein, aber wenn Sie mit .NET/.NET Core Framework gearbeitet haben, haben Sie eine Implementierung dieses Prinzips in Form der Abhängigkeitsinjektion (DI) gesehen. Während sie keine identischen Konzepte sind, verhindern DIP hochrangige Module, die Details seiner Module auf niedriger Ebene zu kennen und sie einzurichten. Es kann dies durch di erreichen. Ein großer Vorteil davon ist, dass es die Kopplung zwischen Modulen reduziert. Die Kopplung ist ein sehr schlechtes Entwicklungsmuster, da es Ihren Code schwierig macht, die Refactor zu übernehmen.
Schlecht:
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 ( ) ;
}
}Gut:
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 ( ) ;
}
}
}⬆ Zurück nach oben
Versuchen Sie, das trockene Prinzip zu beobachten.
Geben Sie Ihr absolutes Bestes, um einen doppelten Code zu vermeiden. Der doppelte Code ist schlecht, da er mehr als einen Ort gibt, an dem Sie etwas ändern können, wenn Sie eine Logik ändern müssen.
Stellen Sie sich vor, Sie führen ein Restaurant und verfolgen Ihr Inventar: alle Ihre Tomaten, Zwiebeln, Knoblauch, Gewürze usw. Wenn Sie mehrere Listen haben, die Sie aufbewahren, müssen alle aktualisiert werden, wenn Sie ein Gericht mit Tomaten in ihnen servieren. Wenn Sie nur eine Liste haben, gibt es nur einen Ort zum Aktualisieren!
Oft haben Sie einen doppelten Code, weil Sie zwei oder mehr etwas andere Dinge haben, die viel gemeinsam haben, aber ihre Unterschiede zwingen Sie zu zwei oder mehr separaten Funktionen, die viel von den gleichen Dingen tun. Das Entfernen von doppelten Code bedeutet, eine Abstraktion zu erstellen, die diesen Satz verschiedener Dinge mit nur einer Funktion/einem Modul/einer Klasse/Klasse verarbeiten kann.
Das Richtige der Abstraktion ist kritisch. Deshalb sollten Sie den festen Prinzipien im Abschnitt Klassen befolgen. Schlechte Abstraktionen können schlechter sein als doppelter Code. Seien Sie also vorsichtig! Trotzdem tun Sie es, wenn Sie eine gute Abstraktion machen können! Wiederholen Sie sich nicht, sonst aktualisieren Sie mehrere Orte, wenn Sie eine Sache ändern möchten.
Schlecht:
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 ) ;
}
}Gut:
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 ) ;
}
}Sehr gut:
Es ist besser, eine kompakte Version des Codes zu verwenden.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ Zurück nach oben
Testen sind wichtiger als der Versand. Wenn Sie keine Tests oder eine unzureichende Menge haben, werden Sie jedes Mal, wenn Sie Code versenden, nicht sicher, dass Sie nichts gebrochen haben. Die Entscheidung, was eine angemessene Menge ausmacht, liegt bei Ihrem Team, aber eine 100% ige Abdeckung (alle Aussagen und Zweige) ist, wie Sie ein sehr hohes Vertrauen und den Entwicklerfrieden erreichen. Dies bedeutet, dass Sie zusätzlich zu einem großartigen Test -Framework auch ein gutes Deckungswerkzeug verwenden müssen.
Es gibt keine Entschuldigung, keine Tests zu schreiben. Es gibt viele gute .NET -Test -Frameworks. Finden Sie also eine, die Ihr Team bevorzugt. Wenn Sie eine finden, die für Ihr Team funktioniert, möchten Sie immer Tests für jedes neue Funktion/jedes neue Modul schreiben, das Sie vorstellen. Wenn Ihre bevorzugte Methode die testgetriebene Entwicklung (TDD) ist, ist dies großartig, aber der Hauptpunkt besteht darin, nur sicherzustellen, dass Sie Ihre Abdeckziele erreichen, bevor Sie eine Funktion auf den Markt bringen oder eine vorhandene neu aufstellen.
Stellt sicher, dass Ihre Tests von Laser fokussiert sind und nicht testen, wie verschiedene (nicht verwandte) Dinge (nicht verwandte) Dinge getestet werden.
Schlecht:
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 ) ;
}
}Gut:
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
⬆ Zurück nach oben
Zusammenfassung der asynchronen Programmierrichtlinien
| Name | Beschreibung | Ausnahmen |
|---|---|---|
| Vermeiden Sie asynchronen Leere | Bevorzugen | Event -Handler |
| Asynchronisiert den ganzen Weg | Mischen Sie Blockierung und asynchronisiertes Code nicht | Konsole -Hauptmethode (C# <= 7.0) |
| Kontext konfigurieren | Verwenden Sie ConfigureAwait(false) wenn Sie können | Methoden, die einen Kontext erfordern |
Die asynchronische Art, Dinge zu tun
| Um das zu tun ... | Anstelle davon ... | Verwenden Sie dies |
|---|---|---|
| Rufen Sie das Ergebnis einer Hintergrundaufgabe ab | Task.Wait or Task.Result | await |
| Warten Sie, bis eine Aufgabe abgeschlossen ist | Task.WaitAny | await Task.WhenAny |
| Abrufen Sie die Ergebnisse mehrerer Aufgaben ab | Task.WaitAll | await Task.WhenAll |
| Warten Sie einen Zeitraum | Thread.Sleep | await Task.Delay |
Beste Practice
Das Async/Awesait ist das Beste für IO -gebundene Aufgaben (Netzwerkkommunikation, Datenbankkommunikation, HTTP -Anfrage usw.), aber es ist nicht gut, sich auf rechnerische gebundene Aufgaben anzuwenden (durchqueren Sie die riesige Liste, machen Sie ein Hugge -Bild usw.). Da das Halten -Thread an den Thread -Pool und die verfügbaren CPU/Kerne abgelassen werden, werden diese Aufgaben nicht bearbeitet. Daher sollten wir es vermeiden, Async/Wartee für komputionelle gebundene Aufgaben zu verwenden.
Für den Umgang mit rechnergestützten Aufgaben bevorzugen Sie es, Task.Factory.CreateNew mit TaskCreationOptions LongRunning zu verwenden. Es wird einen neuen Hintergrund -Thread starten, um eine schwere rechnerisch gebundene Aufgabe zu verarbeiten, ohne sie wieder in den Thread -Pool zu veröffentlichen, bis die Aufgabe abgeschlossen ist.
Kennen Sie Ihre Werkzeuge
Es gibt viel zu lernen über Asynchronisation und erwartet, und es ist natürlich, ein wenig desorientiert zu werden. Hier finden Sie eine kurze Referenz von Lösungen auf gemeinsame Probleme.
Lösungen für gemeinsame asynchronisierte Probleme
| Problem | Lösung |
|---|---|
| Erstellen Sie eine Aufgabe, um Code auszuführen | Task.Run oder TaskFactory.StartNew (nicht der Task -Konstruktor oder Task.Start ) |
| Erstellen Sie einen Aufgabenwrapper für eine Operation oder ein Ereignis | TaskFactory.FromAsync oder TaskCompletionSource<T> |
| Unterstützung Stornierung | CancellationTokenSource und CancellationToken |
| Fortschritt melden | IProgress<T> und Progress<T> |
| Datenströme verarbeiten | TPL -Datenfluss oder reaktive Erweiterungen |
| Synchronisieren Sie den Zugriff auf eine gemeinsame Ressource | SemaphoreSlim |
| Asynchron initialisieren eine Ressource | AsyncLazy<T> |
| Async-fähige Produzenten/Verbraucherstrukturen | TPL DataFlow oder AsyncCollection<T> |
Lesen Sie das aufgabenbasierte asynchrone Muster-Dokument (TAP). Es ist sehr gut geschrieben und beinhaltet Anleitungen zum API-Design und die ordnungsgemäße Verwendung von Async/Warted (einschließlich Stornierungs- und Fortschrittsberichterstattung).
Es gibt viele neue, wartungsfreundliche Techniken, die anstelle der alten Blockierungstechniken verwendet werden sollten. Wenn Sie eines dieser alten Beispiele in Ihrem neuen asynchronisierten Code haben, machen Sie es falsch (TM):
| Alt | Neu | Beschreibung |
|---|---|---|
task.Wait | await task | Warten/warten Sie, bis eine Aufgabe erledigt wird |
task.Result | await task | Erhalten Sie das Ergebnis einer abgeschlossenen Aufgabe |
Task.WaitAny | await Task.WhenAny | Warten/warten Sie auf eine Sammlung von Aufgaben, die erledigt werden können |
Task.WaitAll | await Task.WhenAll | Warten/warten Sie auf jede Sammlung von Aufgaben, die erledigt werden können |
Thread.Sleep | await Task.Delay | Warten/warten Sie für einen bestimmten Zeitraum |
Task | Task.Run oder TaskFactory.StartNew | Erstellen Sie eine codebasierte Aufgabe |
Quelle https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
⬆ Zurück nach oben
Wurpe Fehler sind eine gute Sache! Sie bedeuten, dass die Laufzeit erfolgreich identifiziert wurde, wenn etwas in Ihrem Programm schief gelaufen ist, und Sie werden Ihnen wissen, indem Sie die Funktionsausführung auf dem aktuellen Stack stoppen, den Prozess (in .NET/.NET CORE) abtöten und Sie in der Konsole mit einer Stack -Trace benachrichtigen.
Wenn Sie nach dem Fangen eine Ausnahme wiederwischen müssen, verwenden Sie dies nur "Werfen", indem Sie diese verwenden. Sie speichern die Stapelspur. Aber in der schlechten Option unten werden Sie die Stapelspur verloren.
Schlecht:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ex ;
}Gut:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ;
}⬆ Zurück nach oben
Nichts mit einem gefangenen Fehler zu tun, gibt Ihnen nicht die Fähigkeit, jemals auf diesen Fehler zu reparieren oder zu reagieren. Das Werfen des Fehlers ist nicht viel besser, wenn er in einem Meer von Dingen, die in die Konsole gedruckt sind, verloren gehen kann. Wenn Sie ein Stück Code in einen try/catch einwickeln, bedeutet dies, dass Sie der Meinung sind, dass dort ein Fehler auftreten kann, und Sie sollten daher einen Plan haben oder einen Codepfad erstellen, wenn er auftritt.
Schlecht:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}Gut:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆ Zurück nach oben
Wenn Sie Maßnahmen gemäß der Art der Ausnahme ergreifen müssen, verwenden Sie mehrere Catch -Blocks besser für die Ausnahmebehandlung.
Schlecht:
try
{
// Do something..
}
catch ( Exception ex )
{
if ( ex is TaskCanceledException )
{
// Take action for TaskCanceledException
}
else if ( ex is TaskSchedulerException )
{
// Take action for TaskSchedulerException
}
}Gut:
try
{
// Do something..
}
catch ( TaskCanceledException ex )
{
// Take action for TaskCanceledException
}
catch ( TaskSchedulerException ex )
{
// Take action for TaskSchedulerException
}⬆ Zurück nach oben
C# ermöglicht die Ausnahme in einem Fangblock mit dem throw . Es ist eine schlechte Übung, eine erwischte Ausnahme mit throw e; . Diese Erklärung setzt die Stapelspur zurück. Verwenden Sie stattdessen throw; . Dadurch bleibt die Stapelspur und liefert einen tieferen Einblick in die Ausnahme. Eine weitere Option ist die Verwendung einer benutzerdefinierten Ausnahme. Einfach eine neue Ausnahme instanziieren und ihre innere Ausnahmeeigenschaft auf die gefangene Ausnahme mit new CustomException("some info", e); . Das Hinzufügen von Informationen zu einer Ausnahme ist eine gute Praxis, da sie beim Debuggen hilft. Wenn das Ziel jedoch darin besteht, eine Ausnahme zu protokollieren, dann verwenden Sie throw; das Geld an den Anrufer weitergeben.
Schlecht:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
logger . LogInfo ( ex ) ;
throw ex ;
}Gut:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw ;
}Gut:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw new CustomException ( error ) ;
}⬆ Zurück nach oben
Schlecht:
Hat viele Code -Formatierungsstile im Projekt. Zum Beispiel ist der Einstellstil space und tab im Projekt gemischt.
Gut:
Definieren und .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⬆ Zurück nach oben
Normalerweise fügen sie nur Geräusche hinzu. Lassen Sie die Funktionen und Variablennamen zusammen mit der richtigen Eindrückung und Formatierung Ihrem Code die visuelle Struktur geben.
Schlecht:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;Schlecht:
#region Scope Model Instantiation
var model = {
menu : 'foo' ,
nav : 'bar'
} ;
#endregion
#region Action setup
void Actions ( ) {
// ...
} ;
#endregionGut:
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
void Actions ( )
{
// ...
} ;⬆ Zurück nach oben
Die Versionskontrolle besteht aus einem bestimmten Grund. Hinterlassen Sie den alten Code in Ihrer Geschichte.
Schlecht:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Gut:
doStuff ( ) ;⬆ Zurück nach oben
Denken Sie daran, verwenden Sie die Versionskontrolle! Es besteht kein toter Code, kommentierte Code und insbesondere für Journal -Kommentare. Verwenden Sie git log um Geschichte zu erhalten!
Schlecht:
/**
* 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 ;
}Gut:
public int Combine ( int a , int b )
{
return a + b ;
}⬆ Zurück nach oben
Kommentare sind eine Entschuldigung, keine Anforderung. Guter Code dokumentiert meistens selbst.
Schlecht:
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 ;
}
}Besser aber immer noch schlecht:
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 ;
}
} Wenn ein Kommentar erklärt, was der Code tut, ist er wahrscheinlich ein nutzloser Kommentar und kann mit einer gut benannten Variablen oder Funktion implementiert werden. Der Kommentar im vorherigen Code könnte durch eine Funktion namens ConvertTo32bitInt ersetzt werden, sodass dieser Kommentar immer noch nutzlos ist. Es wäre jedoch schwierig, durch den Code auszudrücken, warum der Entwickler den DJB2-Hash-Algorithmus anstelle von SHA-1 oder einer anderen Hash-Funktion gewählt hat. In diesem Fall ist ein Kommentar akzeptabel.
Gut:
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 ;
}⬆ Zurück nach oben
Vielen Dank an alle Personen, die bereits zum clean-code-dotnet -Projekt beigetragen haben
Lieben Sie unsere Arbeit und helfen Sie uns, unsere Aktivitäten fortzusetzen? [Backer werden]
Werden Sie Sponsor und bringen Sie Ihr Logo in unserem Readme auf GitHub mit einem Link zu Ihrer Website. [Sponsor werden]
Soweit gesetzlich möglich, hat Thangchung auf alle Urheberrechte und verwandte oder benachbarte Rechte an dieser Arbeit verzichtet.