Si vous avez aimé le projet clean-code-dotnet ou s'il vous a aidé, veuillez donner une étoile pour ce référentiel. Cela aidera non seulement à renforcer notre communauté .NET, mais aussi à améliorer les compétences concernant le code propre pour les développeurs .NET dans le monde entier. Merci beaucoup ?
Consultez mon blog ou dites bonjour sur Twitter!
Principes d'ingénierie logicielle, du livre Clean de Robert C. Martin, adapté pour .NET / .NET Core. Ce n'est pas un guide de style. C'est un guide pour produire des logiciels lisibles, réutilisables et refactorables dans le noyau .NET / .NET.
Tous les principes ne doivent pas être strictement suivis, et encore moins seront universellement contenus. Ce sont des directives et rien de plus, mais ce sont des codifiés sur de nombreuses années d'expérience collective par les auteurs de Clean Code .
Inspiré des listes Clean-Code-Javascript et Clean-Code-Php.
Mauvais:
int d ;Bien:
int daySinceModification ;⬆ Retour en haut
Nommez la variable pour refléter ce pour quoi il est utilisé.
Mauvais:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;Bien:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ Retour en haut
La notation hongroise remonte le type qui est déjà présent dans la déclaration. Ceci est inutile car les IDE modernes identifieront le type.
Mauvais:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;Bien:
int counter ;
string fullName ;
DateTime modifiedDate ;La notation hongroise ne doit pas non plus être utilisée dans les paramètres.
Mauvais:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}Bien:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ Retour en haut
La capitalisation vous en dit long sur vos variables, fonctions, etc. Ces règles sont subjectives, donc votre équipe peut choisir ce qu'elle veut. Le fait est que, peu importe ce que vous choisissez tous, soyez cohérent.
Mauvais:
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 { }Bien:
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 { }⬆ Retour en haut
Il faudra du temps pour étudier la signification des variables et des fonctions lorsqu'elles ne sont pas prononcées.
Mauvais:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}Bien:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆ Retour en haut
Utilisez la notation de camecase pour les paramètres variables et de la méthode.
Mauvais:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}Bien:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ Retour en haut
Les gens qui lisent votre code sont également des programmeurs. Nommer les choses correctement aidera tout le monde sur la même longueur d'onde. Nous ne voulons pas prendre le temps d'expliquer à tout le monde à quoi sert une variable ou une fonction.
Bien
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 ( ) ;
}⬆ Retour en haut
Trop de instructions si Else peut rendre le code difficile à suivre. Explicite est meilleur qu'implicite .
Mauvais:
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 ;
}
}Bien:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}Mauvais:
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" ) ;
}
}Bien:
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 ) ;
}⬆ Retour en haut
Ne forcez pas le lecteur de votre code à traduire ce que signifie la variable. Explicite est meilleur qu'implicite .
Mauvais:
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 ) ;
}Bien:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆ Retour en haut
Les chaînes magiques sont des valeurs de chaîne spécifiées directement dans le code d'application qui ont un impact sur le comportement de l'application. Souvent, ces chaînes finiront par être dupliquées dans le système, et comme ils ne peuvent pas être automatiquement mis à jour à l'aide d'outils de refactorisation, ils deviennent une source commune de bogues lorsque des modifications sont apportées à certaines chaînes mais pas dans d'autres.
Mauvais
if ( userRole == "Admin" )
{
// logic in here
}Bien
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}En utilisant cela, nous n'avons qu'à changer dans le lieu de centralisation et d'autres l'adapteront.
⬆ Retour en haut
Si votre nom de classe / objet vous indique quelque chose, ne le répétez pas dans votre nom de variable.
Mauvais:
public class Car
{
public string CarMake { get ; set ; }
public string CarModel { get ; set ; }
public string CarColor { get ; set ; }
//...
}Bien:
public class Car
{
public string Make { get ; set ; }
public string Model { get ; set ; }
public string Color { get ; set ; }
//...
}⬆ Retour en haut
Mauvais:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;Bien:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆ Retour en haut
Mauvais:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;Bien:
GetUser ( ) ;⬆ Retour en haut
Nous lirons plus de code que nous n'écrirons jamais. Il est important que le code que nous écrivions soit lisible et consultable. En ne nommant pas des variables qui finissent par être significatives pour comprendre notre programme, nous avons blessé nos lecteurs. Rendre vos noms consultables.
Mauvais:
// 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 ( ) ) ;Bien:
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 ( ) ) ;⬆ Retour en haut
Mauvais:
var data = new { Name = "John" , Age = 42 , PersonAccess = 4 } ;
// What the heck is 4 for?
if ( data . PersonAccess == 4 )
{
// do edit ...
}Bien:
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 ...
}⬆ Retour en haut
Mauvais:
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 ) ;
}Bien:
Diminuez la dépendance du regex en nommant les sous-bassins.
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 ) ;
}⬆ Retour en haut
Pas bon:
Ce n'est pas bon parce que breweryName peut être NULL .
Cette opinion est plus compréhensible que la version précédente, mais elle contrôle mieux la valeur de la variable.
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}Bien:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆ Retour en haut
Une fonction produit un effet secondaire s'il fait autre chose que de prendre une valeur et de renvoyer une autre valeur ou des valeurs. Un effet secondaire pourrait être d'écrire dans un fichier, de modifier une variable globale ou de câbler accidentellement tout votre argent à un étranger.
Maintenant, vous devez avoir des effets secondaires dans un programme à l'occasion. Comme l'exemple précédent, vous devrez peut-être écrire dans un fichier. Ce que vous voulez faire, c'est centraliser où vous faites cela. Je n'ai pas plusieurs fonctions et classes qui écrivent dans un fichier particulier. Avoir un service qui le fait. Un et unique.
Le point principal est d'éviter les pièges courants comme le partage de l'état entre les objets sans aucune structure, en utilisant des types de données mutables qui peuvent être écrits par n'importe quoi, et ne pas centraliser où vos effets secondaires se produisent. Si vous pouvez le faire, vous serez plus heureux que la grande majorité des autres programmeurs.
Mauvais:
// 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 McDermottBien:
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⬆ Retour en haut
Mauvais:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}Bien:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ Retour en haut
Cela semble être une tâche impossible. En entendant d'abord cela, la plupart des gens disent: "Comment suis-je censé faire quoi que ce soit sans une déclaration if ?" La réponse est que vous pouvez utiliser le polymorphisme pour réaliser la même tâche dans de nombreux cas. La deuxième question est généralement: "Eh bien, c'est génial, mais pourquoi voudrais-je faire ça?" La réponse est un concept de code propre précédent que nous avons appris: une fonction ne devrait faire qu'une seule chose. Lorsque vous avez des cours et des fonctions qui ont if instructions, vous dites à votre utilisateur que votre fonction fait plus d'une chose. N'oubliez pas, faites une chose.
Mauvais:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}Bien:
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 ( ) ;
}
}⬆ Retour en haut
Mauvais:
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" ) ) ;
}
}Bien:
public Path TravelToTexas ( Traveler vehicle )
{
vehicle . TravelTo ( new Location ( "texas" ) ) ;
}ou
// 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" ) ) ;
}
}⬆ Retour en haut
Mauvais:
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 ;
}Bien:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆ Retour en haut
Un drapeau indique que la méthode a plus d'une responsabilité. Il est préférable que la méthode n'ait qu'une seule responsabilité. Divisez la méthode en deux si un paramètre booléen ajoute plusieurs responsabilités à la méthode.
Mauvais:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}Bien:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆ Retour en haut
Polluting Globals est une mauvaise pratique dans de nombreuses langues, car vous pourriez affronter une autre bibliothèque et l'utilisateur de votre API ne serait pas sage jusqu'à ce qu'ils obtiennent une exception en production. Pensons à un exemple: que se passe-t-il si vous vouliez avoir un tableau de configuration. Vous pouvez écrire une fonction globale comme Config() , mais elle pourrait se heurter à une autre bibliothèque qui a essayé de faire la même chose.
Mauvais:
public Dictionary < string , string > Config ( )
{
return new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ;
}Bien:
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 ;
}
} Chargez la configuration et créez une instance de classe Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; Et maintenant, vous devez utiliser l'instance de Configuration dans votre application.
⬆ Retour en haut
Singleton est un anti-motif. Paraphrasé de Brian Button:
Il y a aussi de très bonnes pensées de Misko Hevery sur la racine du problème.
Mauvais:
class DBConnection
{
private static DBConnection _instance ;
private DBConnection ( )
{
// ...
}
public static GetInstance ( )
{
if ( _instance == null )
{
_instance = new DBConnection ( ) ;
}
return _instance ;
}
// ...
}
var singleton = DBConnection . GetInstance ( ) ;Bien:
class DBConnection
{
public DBConnection ( IOptions < DbConnectionOption > options )
{
// ...
}
// ...
} Créez une instance de la classe DBConnection et configurez-la avec le modèle d'option.
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; Et maintenant, vous devez utiliser l'instance de DBConnection dans votre application.
⬆ Retour en haut
Limiter la quantité de paramètres de fonction est incroyablement important car il facilite le test de votre fonction. Avoir plus de trois mène à une explosion combinatoire où vous devez tester des tonnes de cas différents avec chaque argument séparé.
Les arguments zéro sont le cas idéal. Un ou deux arguments sont OK et trois doivent être évités. Rien de plus que cela devrait être consolidé. Habituellement, si vous avez plus de deux arguments, votre fonction essaie d'en faire trop. Dans les cas où ce n'est pas le cas, la plupart du temps, un objet de niveau supérieur suffira comme un argument.
Mauvais:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}Bien:
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 )
{
// ...
}⬆ Retour en haut
C'est de loin la règle la plus importante en génie logiciel. Lorsque les fonctions font plus d'une chose, elles sont plus difficiles à composer, à tester et à raisonner. Lorsque vous pouvez isoler une fonction à une seule action, ils peuvent être refactorisés facilement et votre code lira beaucoup plus propre. Si vous n'éloignez rien d'autre de ce guide autre que cela, vous serez en avance sur de nombreux développeurs.
Mauvais:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}Bien:
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" ) ;
}⬆ Retour en haut
Mauvais:
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 ( ) ;Bien:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆ Retour en haut
Pas encore fini
Lorsque vous avez plus d'un niveau d'abstraction, votre fonction en fait généralement trop. La division des fonctions conduit à une réutilisabilité et à des tests plus faciles.
Mauvais:
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...
}
}Mauvais aussi:
Nous avons effectué une partie de la fonctionnalité, mais la fonction ParseBetterJSAlternative() est toujours très complexe et non testable.
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...
}
}Bien:
La meilleure solution consiste à déplacer les dépendances de la fonction 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...
}
}
}⬆ Retour en haut
Si une fonction en appelle une autre, gardez ces fonctions verticalement proches dans le fichier source. Idéalement, gardez l'appelant juste au-dessus de la Callee. Nous avons tendance à lire le code de haut en bas, comme un journal. Pour cette raison, faites lire votre code de cette façon.
Mauvais:
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 ( ) ;Bien:
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 ( ) ;⬆ Retour en haut
Mauvais:
if ( article . state == "published" )
{
// ...
}Bien:
if ( article . IsPublished ( ) )
{
// ...
}⬆ Retour en haut
Le code mort est tout aussi mauvais que le code en double. Il n'y a aucune raison de le garder dans votre base de code. S'il ne s'appelle pas, débarrassez-vous! Il sera toujours en sécurité dans l'historique de votre version si vous en avez encore besoin.
Mauvais:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;Bien:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆ Retour en haut
En C # / VB.NET, vous pouvez définir des mots clés public , protected et private pour les méthodes. En utilisant, vous pouvez contrôler la modification des propriétés sur un objet.
set .De plus, cela fait partie du principe ouvert / fermé, des principes de conception orientés objet.
Mauvais:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;Bien:
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 ;⬆ Retour en haut
Mauvais:
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 DoeBien:
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⬆ Retour en haut
Ce modèle est très utile et couramment utilisé dans de nombreuses bibliothèques. Il permet à votre code d'être expressif et moins verbeux. Pour cette raison, utilisez le chaînage de la méthode et jetez un œil à la propreté de votre code.
Bien:
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 ( ) ;
}⬆ Retour en haut
Comme indiqué célèbre dans les modèles de conception par le gang de quatre, vous devez préférer la composition à l'héritage où vous pouvez. Il y a beaucoup de bonnes raisons d'utiliser l'héritage et de nombreuses bonnes raisons d'utiliser la composition.
Le point principal de cette maxime est que si votre esprit va instinctivement pour l'héritage, essayez de penser si la composition pouvait mieux modéliser votre problème. Dans certains cas, cela peut.
Vous vous demandez peut-être alors: "Quand devrais-je utiliser l'héritage?" Cela dépend de votre problème à accomplir, mais c'est une liste décente du moment où l'héritage a plus de sens que la composition:
Mauvais:
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 )
{
// ...
}
// ...
}Bien:
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 ) ;
}
// ...
}⬆ Retour en haut
Solid est l'acronyme mnémonique introduit par Michael Feathers pour les cinq premiers principes nommés par Robert Martin, qui signifiait cinq principes de base de la programmation et de la conception orientées objet.
Comme indiqué dans Clean Code, "il ne devrait jamais y avoir plus d'une raison pour laquelle une classe change". Il est tentant de prendre un cours avec beaucoup de fonctionnalités, comme lorsque vous ne pouvez prendre qu'une seule valise sur votre vol. Le problème avec cela est que votre classe ne sera pas conceptuellement cohérente et qu'il lui donnera de nombreuses raisons de changer. Il est important de minimiser le nombre de fois où vous devez changer une classe.
C'est important car si trop de fonctionnalités se trouve dans une classe et que vous en modifiez un morceau, il peut être difficile de comprendre comment cela affectera d'autres modules dépendants de votre base de code.
Mauvais:
class UserSettings
{
private User User ;
public UserSettings ( User user )
{
User = user ;
}
public void ChangeSettings ( Settings settings )
{
if ( verifyCredentials ( ) )
{
// ...
}
}
private bool VerifyCredentials ( )
{
// ...
}
}Bien:
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 ( ) )
{
// ...
}
}
}⬆ Retour en haut
Comme indiqué par Bertrand Meyer, "les entités logicielles (classes, modules, fonctions, etc.) doivent être ouvertes à l'extension, mais fermées pour la modification." Qu'est-ce que cela signifie cependant? Ce principe indique essentiellement que vous devez permettre aux utilisateurs d'ajouter de nouvelles fonctionnalités sans modifier le code existant.
Mauvais:
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
}
}Bien:
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 ) ;
}
}⬆ Retour en haut
Il s'agit d'un terme effrayant pour un concept très simple. Il est officiellement défini comme "Si S est un sous-type de T, alors les objets de type T peuvent être remplacés par des objets de type S (c'est-à-dire que les objets de type S peuvent remplacer les objets de type T) sans modifier aucune des propriétés souhaitables de ce programme (correction, tâche effectuée, etc.)." C'est une définition encore plus effrayante.
La meilleure explication à ce sujet est que si vous avez une classe de parents et une classe d'enfants, alors la classe de base et la classe d'enfants peuvent être utilisées de manière interchangeable sans obtenir de résultats incorrects. Cela pourrait encore être déroutant, alors jetons un coup d'œil à l'exemple classique carré-reclanglement. Mathématiquement, un carré est un rectangle, mais si vous le modélisez en utilisant la relation "IS-A" via l'héritage, vous avez rapidement des ennuis.
Mauvais:
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 ) ;Bien:
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 ) ;⬆ Retour en haut
ISP déclare que "les clients ne devraient pas être obligés de dépendre des interfaces qu'ils n'utilisent pas".
Un bon exemple à examiner qui démontre ce principe concerne les classes qui nécessitent des objets de paramètres importants. Ne pas demander aux clients de configurer d'énormes quantités d'options est bénéfique, car la plupart du temps, ils n'auront pas besoin de tous les paramètres. Les rendre facultatifs aide à prévenir d'avoir une "interface graisseuse".
Mauvais:
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
}
}Bien:
Tous les travailleurs ne sont pas un employé, mais chaque employé est un travailleur.
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
}
}⬆ Retour en haut
Ce principe indique deux choses essentielles:
Cela peut être difficile à comprendre au début, mais si vous avez travaillé avec .NET / .NET Core Framework, vous avez vu une implémentation de ce principe sous forme d'injection de dépendance (DI). Bien qu'ils ne soient pas des concepts identiques, la DIP empêche les modules de haut niveau de connaître les détails de ses modules de bas niveau et de les configurer. Il peut y parvenir via DI. Un énorme avantage est qu'il réduit le couplage entre les modules. Le couplage est un très mauvais schéma de développement car il rend votre code difficile à refactor.
Mauvais:
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 ( ) ;
}
}Bien:
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 ( ) ;
}
}
}⬆ Retour en haut
Essayez d'observer le principe sec.
Faites de votre mieux pour éviter le code en double. Le code en double est mauvais car cela signifie qu'il y a plus d'un endroit pour modifier quelque chose si vous avez besoin de modifier une logique.
Imaginez si vous dirigez un restaurant et que vous gardez une trace de votre inventaire: toutes vos tomates, oignons, ail, épices, etc. Si vous avez plusieurs listes sur lesquelles vous gardez cela, alors tous doivent être mis à jour lorsque vous servez un plat avec des tomates. Si vous n'avez qu'une seule liste, il n'y a qu'un seul endroit pour mettre à jour!
Souvent, vous avez du code en double parce que vous avez deux ou plusieurs choses légèrement différentes, qui partagent beaucoup en commun, mais leurs différences vous obligent à avoir deux ou plusieurs fonctions distinctes qui font la plupart des mêmes choses. La suppression du code en double signifie la création d'une abstraction qui peut gérer cet ensemble de différentes choses avec une seule fonction / module / classe.
Il est essentiel d'obtenir l'abstraction, c'est pourquoi vous devez suivre les principes solides disposés dans la section des classes. Les mauvaises abstractions peuvent être pires que le code en double, alors soyez prudent! Cela dit, si vous pouvez faire une bonne abstraction, faites-le! Ne vous répétez pas, sinon vous vous retrouverez à mettre à jour plusieurs endroits chaque fois que vous voulez changer une chose.
Mauvais:
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 ) ;
}
}Bien:
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 ) ;
}
}Très bien:
Il est préférable d'utiliser une version compacte du code.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ Retour en haut
Les tests sont plus importants que l'expédition. Si vous n'avez pas de tests ou de montant inadéquat, chaque fois que vous expédiez du code, vous ne serez pas sûr de ne rien casser. Décider de ce qui constitue un montant adéquat appartient à votre équipe, mais avoir une couverture à 100% (toutes les déclarations et les succursales) est la façon dont vous obtenez une confiance très élevée et une tranquillité d'esprit du développeur. Cela signifie qu'en plus d'avoir un excellent cadre de test, vous devez également utiliser un bon outil de couverture.
Il n'y a aucune excuse pour ne pas écrire de tests. Il y a beaucoup de bons frameworks de test .NET, alors trouvez-en un que votre équipe préfère. Lorsque vous en trouvez un qui fonctionne pour votre équipe, visez toujours à rédiger des tests pour chaque nouvelle fonctionnalité / module que vous introduisez. Si votre méthode préférée est le développement basé sur les tests (TDD), c'est génial, mais le point principal est de vous assurer que vous atteignez vos objectifs de couverture avant de lancer une fonctionnalité ou de refactoriser une fonctionnalité existante.
S'assure que vos tests sont axés sur le laser et ne testent pas des choses diverses (non liées), forces AAA Patern utilisées pour rendre vos codes plus propres et lisibles.
Mauvais:
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 ) ;
}
}Bien:
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
⬆ Retour en haut
Résumé des directives de programmation asynchrones
| Nom | Description | Exceptions |
|---|---|---|
| Évitez le vide asynchrone | Préférez les méthodes de tâche asynchrammes sur les méthodes de vide asynchrones | Gestionnaires d'événements |
| Asynchrone tout le long | Ne mélangez pas le blocage et le code asynchrone | Méthode principale de la console (C # <= 7.0) |
| Configurer le contexte | Utilisez ConfigureAwait(false) lorsque vous pouvez | Méthodes qui nécessitent un contexte |
La façon asynchrone de faire les choses
| Pour faire ça ... | Au lieu de ça ... | Utilisez ceci |
|---|---|---|
| Récupérer le résultat d'une tâche de fond | Task.Wait or Task.Result | await |
| Attendez que toute tâche se termine | Task.WaitAny | await Task.WhenAny |
| Récupérer les résultats de plusieurs tâches | Task.WaitAll | await Task.WhenAll |
| Attendez une période de temps | Thread.Sleep | await Task.Delay |
Meilleure pratique
L'Async / Await est le meilleur pour les tâches liées aux IO (communication de réseautage, communication de la base de données, demande HTTP, etc.), mais il n'est pas bon de s'appliquer sur les tâches liées à la calcul (traverser sur l'énorme liste, rendre une image HUGGE, etc.). Car il libérera le fil de maintien du pool de threads et du processeur / cœurs disponibles n'impliqueront pas pour traiter ces tâches. Par conséquent, nous devons éviter d'utiliser l'async / attendre pour les tâches liées à la composition.
Pour gérer les tâches liées à la calcul, préférez utiliser Task.Factory.CreateNew with TaskCreationOptions est LongRunning . Il démarrera un nouveau thread d'arrière-plan pour traiter une lourde tâche de calcul de calcul sans la remettre au pool de threads jusqu'à la fin de la tâche.
Connaissez vos outils
Il y a beaucoup à apprendre sur l'async et à attendre, et il est naturel de se désorienter un peu. Voici une référence rapide des solutions aux problèmes courants.
Solutions aux problèmes asynchrones communs
| Problème | Solution |
|---|---|
| Créer une tâche pour exécuter du code | Task.Run ou TaskFactory.StartNew (pas le constructeur Task ou Task.Start ) |
| Créer un emballage de tâche pour une opération ou un événement | TaskFactory.FromAsync ou TaskCompletionSource<T> |
| Annulation de soutien | CancellationTokenSource et CancellationToken |
| Signaler les progrès | IProgress<T> et Progress<T> |
| Gérer les flux de données | TPL Dataflow ou extensions réactives |
| Synchroniser l'accès à une ressource partagée | SemaphoreSlim |
| Initialiser de manière asynchrone une ressource | AsyncLazy<T> |
| Structures productrices / consommateurs pratiquées en asynchronisation | TPL Dataflow ou AsyncCollection<T> |
Lisez le document de modèle asynchrone basé sur les tâches (TAP). Il est extrêmement bien écrit et comprend des conseils sur la conception de l'API et l'utilisation appropriée de l'async / attend (y compris l'annulation et les rapports d'avancement).
Il existe de nombreuses nouvelles techniques adaptées à l'attente qui devraient être utilisées au lieu des anciennes techniques de blocage. Si vous avez l'un de ces anciens exemples dans votre nouveau code asynchrone, vous vous trompez (TM):
| Vieux | Nouveau | Description |
|---|---|---|
task.Wait | await task | Attendez / attendez une tâche pour terminer |
task.Result | await task | Obtenez le résultat d'une tâche terminée |
Task.WaitAny | await Task.WhenAny | Attendez / attendez l'une d'une collection de tâches à accomplir |
Task.WaitAll | await Task.WhenAll | Attendez / attendez pour chacune d'une collection de tâches à accomplir |
Thread.Sleep | await Task.Delay | Attendre / attendre pendant un certain temps |
Constructeur Task | Task.Run ou TaskFactory.StartNew | Créer une tâche basée sur le code |
Source https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
⬆ Retour en haut
Les erreurs lancées sont une bonne chose! Ils signifient que le temps d'exécution s'est identifié avec succès lorsque quelque chose dans votre programme a mal tourné et qu'il vous fait savoir en arrêtant l'exécution de la fonction sur la pile actuelle, en tuant le processus (dans .NET / .NET Core) et en vous informant dans la console avec une trace de pile.
Si vous avez besoin de remédier à une exception après l'attraper, utilisez simplement «lancer» en utilisant ceci, vous enregistrerez la trace de pile. Mais dans la mauvaise option ci-dessous, vous perdrez la trace de pile.
Mauvais:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ex ;
}Bien:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ;
}⬆ Retour en haut
Ne rien faire avec une erreur capturée ne vous donne pas la possibilité de réparer ou de réagir à cette erreur. Lancer l'erreur n'est pas beaucoup mieux car souvent, il peut se perdre dans une mer de choses imprimées dans la console. Si vous enveloppez un bit de code dans un try/catch cela signifie que vous pensez qu'une erreur peut s'y produire et que vous devez donc avoir un plan ou créer un chemin de code, pour quand il se produit.
Mauvais:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}Bien:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆ Retour en haut
Si vous avez besoin d'agir en fonction du type d'exception, vous mieux utiliser plusieurs blocs de capture pour la gestion des exceptions.
Mauvais:
try
{
// Do something..
}
catch ( Exception ex )
{
if ( ex is TaskCanceledException )
{
// Take action for TaskCanceledException
}
else if ( ex is TaskSchedulerException )
{
// Take action for TaskSchedulerException
}
}Bien:
try
{
// Do something..
}
catch ( TaskCanceledException ex )
{
// Take action for TaskCanceledException
}
catch ( TaskSchedulerException ex )
{
// Take action for TaskSchedulerException
}⬆ Retour en haut
C # permet à l'exception d'être refait dans un bloc de capture à l'aide du mot- throw . C'est une mauvaise pratique de lancer une exception capturée en utilisant throw e; . Cette instruction réinitialise la trace de pile. Utilisez plutôt throw; . Cela gardera la trace de pile et fournira un aperçu plus profond de l'exception. Une autre option consiste à utiliser une exception personnalisée. Instanciez simplement une nouvelle exception et définissez sa propriété d'exception intérieure à l'exception capturée avec lancez new CustomException("some info", e); . L'ajout d'informations à une exception est une bonne pratique car elle aidera à déboguer. Cependant, si l'objectif est de enregistrer une exception, utilisez throw; pour passer le mâle à l'appelant.
Mauvais:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
logger . LogInfo ( ex ) ;
throw ex ;
}Bien:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw ;
}Bien:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw new CustomException ( error ) ;
}⬆ Retour en haut
Mauvais:
A de nombreux styles de formatage de code dans le projet. Par exemple, le style de retrait est space et tab mélangés dans le projet.
Bien:
Définissez et maintenez le style de code cohérent dans votre base de code avec l'utilisation d'un fichier .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⬆ Retour en haut
Ils ajoutent généralement du bruit. Laissez les fonctions et les noms de variables ainsi que l'indentation et la mise en forme appropriés donner la structure visuelle à votre code.
Mauvais:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;Mauvais:
#region Scope Model Instantiation
var model = {
menu : 'foo' ,
nav : 'bar'
} ;
#endregion
#region Action setup
void Actions ( ) {
// ...
} ;
#endregionBien:
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
void Actions ( )
{
// ...
} ;⬆ Retour en haut
Le contrôle de la version existe pour une raison. Laissez le vieux code dans votre histoire.
Mauvais:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Bien:
doStuff ( ) ;⬆ Retour en haut
N'oubliez pas, utilisez le contrôle de version! Il n'y a pas besoin de code mort, de code commenté, et en particulier des commentaires du journal. Utilisez git log pour obtenir l'histoire!
Mauvais:
/**
* 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 ;
}Bien:
public int Combine ( int a , int b )
{
return a + b ;
}⬆ Retour en haut
Les commentaires sont des excuses, pas une exigence. Un bon code documente principalement .
Mauvais:
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 ;
}
}Mieux mais toujours mauvais:
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 ;
}
} Si un commentaire explique ce que fait le code, il s'agit probablement d'un commentaire inutile et peut être implémenté avec une variable bien nommée ou une fonction. Le commentaire dans le code précédent pourrait être remplacé par une fonction nommée ConvertTo32bitInt donc ce commentaire est toujours inutile. Cependant, il serait difficile d'exprimer par code pourquoi le développeur a choisi l'algorithme de hachage DJB2 au lieu de SHA-1 ou d'une autre fonction de hachage. Dans ce cas, un commentaire est acceptable.
Bien:
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 ;
}⬆ Retour en haut
Merci à toutes les personnes qui ont déjà contribué au projet clean-code-dotnet
Aimez notre travail et aidez-nous à poursuivre nos activités? [Devenez un bailleur de fonds]
Devenez sponsor et obtenez votre logo dans notre lecture sur GitHub avec un lien vers votre site. [Devenir sponsor]
Dans la mesure du possible en vertu de la loi, Thangchung a renoncé à tous les droits d'auteur et aux droits connexes ou voisins de ce travail.