Si le gustó el proyecto clean-code-dotnet o si le ayudó, entregue una estrella para este repositorio. Eso no solo ayudará a fortalecer nuestra comunidad .NET, sino que también mejorará las habilidades sobre el código limpio para los desarrolladores de .NET en todo el mundo. Muchas gracias ?
¡Mira mi blog o saluda en Twitter!
Principios de ingeniería de software, del código de limpieza del libro de Robert C. Martin, adaptado para .NET/.NET Core. Esta no es una guía de estilo. Es una guía para producir software legible, reutilizable y refactorable en .NET/.NET Core.
No todos los principios en este documento deben seguirse estrictamente, y incluso menos serán acordados universalmente. Estas son pautas y nada más, pero están codificadas durante muchos años de experiencia colectiva por parte de los autores de Clean Code .
Inspirado en listas Clean-Code-JavaScript y Clean-Code-PHP.
Malo:
int d ;Bien:
int daySinceModification ;⬆ De vuelta a la cima
Nombre la variable para reflejar para qué se usa.
Malo:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;Bien:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ De vuelta a la cima
La notación húngara reafirma el tipo que ya está presente en la declaración. Esto no tiene sentido ya que los IDE modernos identificarán el tipo.
Malo:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;Bien:
int counter ;
string fullName ;
DateTime modifiedDate ;La notación húngara tampoco debe usarse en parámetros.
Malo:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}Bien:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ De vuelta a la cima
La capitalización le dice mucho sobre sus variables, funciones, etc. Estas reglas son subjetivas, por lo que su equipo puede elegir lo que desee. El punto es que, no importa lo que todos elijan, solo sea consistente.
Malo:
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 { }⬆ De vuelta a la cima
Tomará tiempo investigar el significado de las variables y funciones cuando no son pronunciables.
Malo:
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 ; }
}⬆ De vuelta a la cima
Use la notación de CamelCase para parámetros variables y de métodos.
Malo:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}Bien:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ De vuelta a la cima
Las personas que leen su código también son programadores. Nombrar las cosas bien ayudará a todos a estar en la misma página. No queremos tomarnos el tiempo para explicar a todos para qué es una variable o función.
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 ( ) ;
}⬆ De vuelta a la cima
Demasiadas declaraciones si más pueden hacer que el código sea difícil de seguir. Explícito es mejor que implícito .
Malo:
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 ( ) ) ;
}Malo:
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 ) ;
}⬆ De vuelta a la cima
No obligue al lector de su código a traducir lo que significa la variable. Explícito es mejor que implícito .
Malo:
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 ) ;
}⬆ De vuelta a la cima
Las cadenas mágicas son valores de cadena que se especifican directamente dentro del código de aplicación que tienen un impacto en el comportamiento de la aplicación. Con frecuencia, tales cadenas terminarán duplicadas dentro del sistema, y dado que no pueden actualizarse automáticamente utilizando herramientas de refactorización, se convierten en una fuente común de errores cuando se realizan cambios en algunas cadenas pero no en otras.
Malo
if ( userRole == "Admin" )
{
// logic in here
}Bien
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}Usando esto, solo tenemos que cambiar en el lugar centralizado y otros lo adaptarán.
⬆ De vuelta a la cima
Si su nombre de clase/objeto le dice algo, no repita eso en su nombre de variable.
Malo:
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 ; }
//...
}⬆ De vuelta a la cima
Malo:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;Bien:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆ De vuelta a la cima
Malo:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;Bien:
GetUser ( ) ;⬆ De vuelta a la cima
Leeremos más código del que escribiremos. Es importante que el código que escribimos sea legible y se puede buscar. Al no nombrar variables que terminan siendo significativas para comprender nuestro programa, lastimamos a nuestros lectores. Haga que sus nombres sean buscando.
Malo:
// 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 ( ) ) ;⬆ De vuelta a la cima
Malo:
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 ...
}⬆ De vuelta a la cima
Malo:
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:
Disminuya la dependencia de RegEx al nombrar subpaterarios.
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 ) ;
}⬆ De vuelta a la cima
No es bueno:
Esto no es bueno porque breweryName puede ser NULL .
Esta opinión es más comprensible que la versión anterior, pero controla mejor el valor 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." )
{
// ...
}⬆ De vuelta a la cima
Una función produce un efecto secundario si hace algo más que tomar un valor y devolver otro valor o valores. Un efecto secundario podría ser escribir en un archivo, modificar alguna variable global o cablear accidentalmente todo su dinero a un extraño.
Ahora, debe tener efectos secundarios en un programa en ocasiones. Al igual que el ejemplo anterior, es posible que deba escribir en un archivo. Lo que quieres hacer es centralizar dónde estás haciendo esto. No tenga varias funciones y clases que escriban a un archivo en particular. Tener un servicio que lo haga. Uno y solo uno.
El punto principal es evitar las dificultades comunes como compartir el estado entre objetos sin ninguna estructura, usar tipos de datos mutables que cualquier cosa pueda ser escrito por cualquier cosa y no centralizar dónde ocurren sus efectos secundarios. Si puede hacer esto, será más feliz que la gran mayoría de otros programadores.
Malo:
// 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⬆ De vuelta a la cima
Malo:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}Bien:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ De vuelta a la cima
Esto parece una tarea imposible. Al escuchar esto, la mayoría de la gente dice: "¿Cómo se supone que debo hacer algo sin una declaración if ?" La respuesta es que puede usar el polimorfismo para lograr la misma tarea en muchos casos. La segunda pregunta suele ser, "bueno, eso es genial, pero ¿por qué querría hacer eso?" La respuesta es un concepto de código limpio anterior que aprendimos: una función solo debe hacer una cosa. Cuando tiene clases y funciones que tienen declaraciones if , le está diciendo a su usuario que su función hace más de una cosa. Recuerda, solo haz una cosa.
Malo:
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 ( ) ;
}
}⬆ De vuelta a la cima
Malo:
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" ) ) ;
}o
// 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" ) ) ;
}
}⬆ De vuelta a la cima
Malo:
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 ;
}⬆ De vuelta a la cima
Una bandera indica que el método tiene más de una responsabilidad. Es mejor si el método solo tiene una sola responsabilidad. Divida el método en dos si un parámetro booleano agrega múltiples responsabilidades al método.
Malo:
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 ) ;
}⬆ De vuelta a la cima
La contaminación de Globals es una mala práctica en muchos idiomas porque podría chocar con otra biblioteca y el usuario de su API no sería más sabio hasta que obtengan una excepción en la producción. Pensemos en un ejemplo: ¿qué pasaría si quisiera tener una matriz de configuración? Puede escribir funciones globales como Config() , pero podría chocar con otra biblioteca que intentó hacer lo mismo.
Malo:
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 ;
}
} Cargar la configuración y crear instancia de la clase Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; Y ahora debe usar la instancia de Configuration en su aplicación.
⬆ De vuelta a la cima
Singleton es un antipatrón. Parafraseado del botón Brian:
También hay muy buenos pensamientos de Misko Hevery sobre la raíz del problema.
Malo:
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 )
{
// ...
}
// ...
} Cree una instancia de la clase DBConnection y configúrela con el patrón de opción.
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; Y ahora debe usar la instancia de DBConnection en su aplicación.
⬆ De vuelta a la cima
Limitar la cantidad de parámetros de la función es increíblemente importante porque facilita la prueba de su función. Tener más de tres conduce a una explosión combinatoria donde debe probar toneladas de diferentes casos con cada argumento separado.
Cero argumentos es el caso ideal. Uno o dos argumentos están bien, y se deben evitar tres. Cualquier cosa más que eso debería consolidarse. Por lo general, si tiene más de dos argumentos, su función está tratando de hacer demasiado. En los casos en que no lo es, la mayoría de las veces un objeto de nivel superior será suficiente como argumento.
Malo:
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 )
{
// ...
}⬆ De vuelta a la cima
Esta es, con mucho, la regla más importante en la ingeniería de software. Cuando las funciones hacen más de una cosa, son más difíciles de componer, probar y razonar. Cuando puede aislar una función a una sola acción, se pueden refactorizar fácilmente y su código se lee mucho más limpio. Si no quita nada más de esta guía que no sea esta, estará por delante de muchos desarrolladores.
Malo:
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" ) ;
}⬆ De vuelta a la cima
Malo:
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 ( ) ;⬆ De vuelta a la cima
No terminado todavía
Cuando tiene más de un nivel de abstracción, su función generalmente está haciendo demasiado. La división de funciones conduce a una reutilización y pruebas más fáciles.
Malo:
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...
}
}Mal también:
Hemos llevado a cabo parte de la funcionalidad, pero la función ParseBetterJSAlternative() sigue siendo muy compleja y no comprobable.
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 mejor solución es mover las dependencias de la función 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...
}
}
}⬆ De vuelta a la cima
Si una función llama a otra, mantenga esas funciones cercanas verticalmente en el archivo de origen. Idealmente, mantenga la persona que llama justo encima de la Callee. Tendemos a leer el código de arriba a abajo, como un periódico. Debido a esto, haga que su código lea de esa manera.
Malo:
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 ( ) ;⬆ De vuelta a la cima
Malo:
if ( article . state == "published" )
{
// ...
}Bien:
if ( article . IsPublished ( ) )
{
// ...
}⬆ De vuelta a la cima
El código muerto es tan malo como el código duplicado. No hay razón para mantenerlo en su base de código. Si no se llama, ¡deshazte de ello! Todavía será seguro en su historial de versiones si aún lo necesita.
Malo:
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" ) ;⬆ De vuelta a la cima
En C# / vb.net puede establecer palabras clave public , protected y private para los métodos. Utilizándolo, puede controlar la modificación de propiedades en un objeto.
set .Además, esto es parte del principio abierto/cerrado, de principios de diseño orientados a objetos.
Malo:
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 ;⬆ De vuelta a la cima
Malo:
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⬆ De vuelta a la cima
Este patrón es muy útil y se usa comúnmente en muchas bibliotecas. Permite que su código sea expresivo y menos detallado. Por esa razón, use el encadenamiento del método y eche un vistazo a cuán limpio será su código.
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 ( ) ;
}⬆ De vuelta a la cima
Como se declara famoso en los patrones de diseño por la pandilla de cuatro, debe preferir la composición sobre la herencia donde pueda. Hay muchas buenas razones para usar la herencia y muchas buenas razones para usar la composición.
El punto principal para esta máxima es que si su mente instintivamente va para la herencia, trate de pensar si la composición podría modelar mejor su problema. En algunos casos puede.
Tal vez se pregunte entonces: "¿Cuándo debería usar la herencia?" Depende de su problema en cuestión, pero esta es una lista decente de cuando la herencia tiene más sentido que la composición:
Malo:
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 ) ;
}
// ...
}⬆ De vuelta a la cima
Solid es el acrónimo mnemónico introducido por Michael Feathers para los primeros cinco principios nombrados por Robert Martin, que significó cinco principios básicos de programación y diseño orientados a objetos.
Como se indica en el código limpio, "nunca debe haber más de una razón para que una clase cambie". Es tentador empacar una clase con mucha funcionalidad, como cuando solo puedes tomar una maleta en tu vuelo. El problema con esto es que su clase no será conceptualmente cohesiva y le dará muchas razones para cambiar. Es importante minimizar la cantidad de veces que necesita cambiar una clase.
Es importante porque si hay demasiada funcionalidad en una clase y modifica una parte de ella, puede ser difícil entender cómo eso afectará a otros módulos dependientes en su base de código.
Malo:
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 ( ) )
{
// ...
}
}
}⬆ De vuelta a la cima
Según lo declarado por Bertrand Meyer, "las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para la extensión, pero cerradas para la modificación". ¿Qué significa eso? Este principio básicamente establece que debe permitir a los usuarios agregar nuevas funcionalidades sin cambiar el código existente.
Malo:
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 ) ;
}
}⬆ De vuelta a la cima
Este es un término aterrador para un concepto muy simple. Se define formalmente como "Si S es un subtipo de T, entonces los objetos de tipo T pueden reemplazarse con objetos de tipo S (es decir, los objetos de tipo S pueden sustituir objetos del Tipo T) sin alterar ninguna de las propiedades deseables de ese programa (corrección, tarea realizada, etc.)". Esa es una definición aún más aterradora.
La mejor explicación para esto es si tiene una clase de padres y una clase infantil, entonces la clase base y la clase infantil se pueden usar indistintamente sin obtener resultados incorrectos. Esto podría ser confuso, así que echemos un vistazo al clásico ejemplo de rectángulo cuadrado. Matemáticamente, un cuadrado es un rectángulo, pero si lo modela utilizando la relación "IS-A" a través de la herencia, rápidamente se mete en problemas.
Malo:
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 ) ;⬆ De vuelta a la cima
ISP afirma que "los clientes no deben verse obligados a depender de las interfaces que no usan".
Un buen ejemplo para ver eso demuestra este principio es para clases que requieren grandes objetos de configuración. No exigir que los clientes configuren grandes cantidades de opciones es beneficioso, porque la mayoría de las veces no necesitarán toda la configuración. Hacerlos opcionales ayuda a evitar tener una "interfaz grasa".
Malo:
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:
No todos los trabajadores son empleados, pero cada empleado es un trabajador.
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
}
}⬆ De vuelta a la cima
Este principio establece dos cosas esenciales:
Esto puede ser difícil de entender al principio, pero si ha trabajado con .NET/.NET Core Framework, ha visto una implementación de este principio en forma de inyección de dependencia (DI). Si bien no son conceptos idénticos, DIP evita que los módulos de alto nivel conozcan los detalles de sus módulos de bajo nivel y los configuren. Puede lograr esto a través de Di. Un gran beneficio de esto es que reduce el acoplamiento entre los módulos. El acoplamiento es un patrón de desarrollo muy malo porque hace que su código sea difícil de refactorizar.
Malo:
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 ( ) ;
}
}
}⬆ De vuelta a la cima
Trate de observar el principio seco.
Haga todo lo posible para evitar el código duplicado. El código duplicado es malo porque significa que hay más de un lugar para alterar algo si necesita cambiar alguna lógica.
Imagínese si dirige un restaurante y realiza un seguimiento de su inventario: todos sus tomates, cebollas, ajo, especias, etc. Si tiene múltiples listas que mantiene esto encendido, entonces todos deben actualizarse cuando sirva un plato con tomates en ellos. Si solo tiene una lista, ¡solo hay un lugar para actualizar!
A menudo tienes código duplicado porque tienes dos o más cosas ligeramente diferentes, que comparten mucho en común, pero sus diferencias te obligan a tener dos o más funciones separadas que hacen las mismas cosas. Eliminar el código duplicado significa crear una abstracción que puede manejar este conjunto de cosas diferentes con una sola función/módulo/clase.
Obtener la abstracción correcta es crítica, por eso debe seguir los principios sólidos establecidos en la sección Classes. Las abstracciones malas pueden ser peores que el código duplicado, ¡así que tenga cuidado! Habiendo dicho esto, si puedes hacer una buena abstracción, ¡hazlo! No se repita, de lo contrario, se encontrará actualizando varios lugares en cualquier momento que desee cambiar una cosa.
Malo:
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 ) ;
}
}Muy bien:
Es mejor usar una versión compacta del código.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ De vuelta a la cima
Las pruebas son más importantes que el envío. Si no tiene pruebas o una cantidad inadecuada, cada vez que envía código no se asegura de no romper nada. Decidir qué constituye una cantidad adecuada depende de su equipo, pero tener una cobertura del 100% (todas las declaraciones y ramas) es cómo logra muy alta confianza y tranquilidad del desarrollador. Esto significa que, además de tener un excelente marco de prueba, también debe usar una buena herramienta de cobertura.
No hay excusa para no escribir pruebas. Hay muchos buenos marcos de prueba .NET, así que busque uno que su equipo prefiera. Cuando encuentre uno que funcione para su equipo, suponga que siempre escriba pruebas para cada nueva característica/módulo que introduzca. Si su método preferido es el desarrollo impulsado por las pruebas (TDD), eso es excelente, pero el punto principal es asegurarse de alcanzar sus objetivos de cobertura antes de lanzar cualquier función o refactorizar una existente.
Asegura que sus pruebas estén enfocadas con láser y no prueben cosas misceláneas (no relacionadas), obliga a la Paterna AAA que se usa para hacer que sus códigos sean más limpios y legibles.
Malo:
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
⬆ De vuelta a la cima
Resumen de las pautas de programación asincrónica
| Nombre | Descripción | Excepciones |
|---|---|---|
| Evite el vacío de asíncrono | Prefiere métodos de tarea asíncrono sobre los métodos de vacío asíncrono | Manipuladores de eventos |
| Asíncrito todo el camino | No mezcle el bloqueo y el código de asíncrono | Método principal de la consola (C# <= 7.0) |
| Configurar contexto | Use ConfigureAwait(false) cuando pueda | Métodos que requieren contexto |
La forma asíncrata de hacer las cosas
| Para hacer esto ... | En lugar de esto ... | Usa esto |
|---|---|---|
| Recuperar el resultado de una tarea de fondo | Task.Wait or Task.Result | await |
| Espere a que se complete cualquier tarea | Task.WaitAny | await Task.WhenAny |
| Recuperar los resultados de múltiples tareas | Task.WaitAll | await Task.WhenAll |
| Espera un período de tiempo | Thread.Sleep | await Task.Delay |
Mejor práctica
El Async/Agait es lo mejor para las tareas de IO (comunicación de redes, comunicación de bases de datos, solicitud HTTP, etc.) pero no es bueno aplicar en las tareas limitadas computacionales (atravesar la enorme lista, representar una imagen de abrazo, etc.). Debido a que liberará el hilo de retención en el grupo de subprocesos y la CPU/núcleos disponibles no implicará procesar esas tareas. Por lo tanto, debemos evitar el uso de async/esperanza para tareas de límite computionales.
Para tratar con tareas límite computacionales, prefiera usar Task.Factory.CreateNew con TaskCreationOptions es LongRunning . Comenzará un nuevo hilo de fondo para procesar una tarea de límite computacional pesado sin liberarla al grupo de subprocesos hasta que se complete la tarea.
Conozca sus herramientas
Hay mucho que aprender sobre Async y esperar, y es natural ponerse un poco desorientado. Aquí hay una referencia rápida de soluciones a problemas comunes.
Soluciones a problemas comunes de asíncrono
| Problema | Solución |
|---|---|
| Crear una tarea para ejecutar código | Task.Run o TaskFactory.StartNew (no el constructor Task o Task.Start . |
| Crear un envoltorio de tareas para una operación o evento | TaskFactory.FromAsync o TaskCompletionSource<T> |
| Soporte de cancelación | CancellationTokenSource y CancellationToken |
| Informe de progreso | IProgress<T> y Progress<T> |
| Manejar flujos de datos | TPL DataFlow o extensiones reactivas |
| Sincronizar el acceso a un recurso compartido | SemaphoreSlim |
| Inicializar asincrónicamente un recurso | AsyncLazy<T> |
| Estructuras de productor/consumidor listas para async | TPL DataFlow o AsyncCollection<T> |
Lea el documento de patrón asíncrono (TAP) basado en tareas. Está extremadamente bien escrito e incluye orientación sobre el diseño de API y el uso adecuado de async/esperanza (incluida la cancelación y los informes de progreso).
Hay muchas nuevas técnicas esperadas que deben usarse en lugar de las viejas técnicas de bloqueo. Si tiene alguno de estos ejemplos antiguos en su nuevo código async, lo está haciendo mal (TM):
| Viejo | Nuevo | Descripción |
|---|---|---|
task.Wait | await task | Esperar/esperar una tarea para completar |
task.Result | await task | Obtenga el resultado de una tarea completa |
Task.WaitAny | await Task.WhenAny | Esperar/esperar para una de una colección de tareas para completar |
Task.WaitAll | await Task.WhenAll | Esperar/esperar por cada una de una colección de tareas para completar |
Thread.Sleep | await Task.Delay | Esperar/esperar por un período de tiempo |
Constructor Task | Task.Run o TaskFactory.StartNew | Crear una tarea basada en código |
Fuente https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
⬆ De vuelta a la cima
¡Los errores lanzados son algo bueno! Significan que el tiempo de ejecución se ha identificado con éxito cuando algo en su programa ha salido mal y le deja saber al detener la ejecución de la función en la pila actual, matar el proceso (en .NET/.NET Core) y notificarse en la consola con un rastro de pila.
Si necesita volver a retirar una excepción después de atraparlo, use solo 'tirar' usando esto, guardará el rastro de la pila. Pero en la mala opción a continuación, perderá el rastro de la pila.
Malo:
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 ;
}⬆ De vuelta a la cima
No hacer nada con un error atrapado no le da la capacidad de solucionar o reaccionar ante dicho error. Lanzar el error no es mucho mejor, ya que a menudo puede perderse en un mar de cosas impresas en la consola. Si envuelve algún bit de código en un try/catch , significa que cree que puede ocurrir un error allí y, por lo tanto, debe tener un plan o crear una ruta de código, para cuando ocurra.
Malo:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}Bien:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆ De vuelta a la cima
Si necesita tomar medidas de acuerdo con el tipo de excepción, es mejor que use un bloque de captura múltiple para el manejo de excepciones.
Malo:
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
}⬆ De vuelta a la cima
C# permite que la excepción se vuelva a llenar en un bloque de captura utilizando la palabra clave throw . Es una mala práctica lanzar una excepción atrapada usando throw e; . Esta declaración restablece el rastro de la pila. En su lugar, use throw; . Esto mantendrá el rastreo de la pila y proporcionará una visión más profunda sobre la excepción. Otra opción es usar una excepción personalizada. Simplemente instanciar una nueva excepción y establecer su propiedad de excepción interna a la excepción atrapada con Show new CustomException("some info", e); . Agregar información a una excepción es una buena práctica, ya que ayudará con la depuración. Sin embargo, si el objetivo es registrar una excepción, entonces use throw; para pasar el dinero a la persona que llama.
Malo:
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 ) ;
}⬆ De vuelta a la cima
Malo:
Tiene muchos estilos de formato de código en el proyecto. Por ejemplo, el estilo de sangría es space y tab mezclados en el proyecto.
Bien:
Definir y mantener un estilo de código constante en su base de código con el uso de un archivo .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⬆ De vuelta a la cima
Por lo general, solo agregan ruido. Deje que las funciones y nombres de variables junto con la sangría y el formato adecuados dan la estructura visual a su código.
Malo:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;Malo:
#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 ( )
{
// ...
} ;⬆ De vuelta a la cima
El control de versiones existe por una razón. Deja el código antiguo en tu historial.
Malo:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Bien:
doStuff ( ) ;⬆ De vuelta a la cima
¡Recuerde, use el control de versiones! No hay necesidad de código muerto, código comentado y especialmente los comentarios de la revista. ¡Use git log para obtener historia!
Malo:
/**
* 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 ;
}⬆ De vuelta a la cima
Los comentarios son una disculpa, no un requisito. El buen código se documenta principalmente .
Malo:
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 ;
}
}Mejor pero aún malo:
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 comentario explica lo que está haciendo el código, probablemente sea un comentario inútil y se puede implementar con una variable o función bien nombrada. El comentario en el código anterior podría reemplazarse con una función llamada ConvertTo32bitInt para que este comentario aún sea inútil. Sin embargo, sería difícil expresar por código por qué el desarrollador eligió el algoritmo de hash DJB2 en lugar de SHA-1 u otra función hash. En ese caso, un comentario es aceptable.
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 ;
}⬆ De vuelta a la cima
Gracias a todas las personas que ya han contribuido al proyecto clean-code-dotnet
¿Amo nuestro trabajo y ayúdanos a continuar nuestras actividades? [Conviértete en un patrocinador]
Conviértase en patrocinador y obtenga su logotipo en nuestro ReadMe en GitHub con un enlace a su sitio. [Convertirse en patrocinador]
En la medida de lo posible según la ley, Thangchung ha renunciado a todos los derechos de autor y derechos relacionados o vecinos para este trabajo.