Se você gostou do projeto de clean-code-dotnet ou se ele o ajudou, dê uma estrela para este repositório. Isso não apenas ajudará a fortalecer nossa comunidade .NET, mas também melhorar as habilidades sobre o código limpo para desenvolvedores .NET em todo o mundo. Muito obrigado ?
Confira meu blog ou diga oi no Twitter!
Princípios de engenharia de software, do código limpo do livro de Robert C. Martin, adaptado para .NET/.NET Core. Este não é um guia de estilo. É um guia para produzir software legível, reutilizável e refatorável no núcleo .NET/.NET.
Nem todo princípio aqui deve ser seguido estritamente, e menos ainda será universalmente acordado. Essas são diretrizes e nada mais, mas são codificadas ao longo de muitos anos de experiência coletiva pelos autores do código limpo .
Inspirado nas listas de codificado limpo e listas de código limpo-php.
Ruim:
int d ;Bom:
int daySinceModification ;⬆ de volta ao topo
Nomeie a variável para refletir para que é usado.
Ruim:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;Bom:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ de volta ao topo
A notação húngara reafirma o tipo que já está presente na declaração. Isso é inútil, pois os IDEs modernos identificarão o tipo.
Ruim:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;Bom:
int counter ;
string fullName ;
DateTime modifiedDate ;A notação húngara também não deve ser usada em parâmetros.
Ruim:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}Bom:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ de volta ao topo
A capitalização diz muito sobre suas variáveis, funções etc. Essas regras são subjetivas, para que sua equipe possa escolher o que quiser. O ponto é que, não importa o que vocês escolham, apenas seja consistente.
Ruim:
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 { }Bom:
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 volta ao topo
Levará tempo para investigar o significado das variáveis e funções quando elas não forem pronunciáveis.
Ruim:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}Bom:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆ de volta ao topo
Use a notação do camelcase para parâmetros variáveis e de método.
Ruim:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}Bom:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ de volta ao topo
As pessoas que leem seu código também são programadores. Nomear as coisas corretamente ajudará todos na mesma página. Não queremos dedicar tempo para explicar a todos para que serve uma variável ou função.
Bom
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 volta ao topo
Muitas declarações, se mais, podem dificultar o código do código. Explícito é melhor do que implícito .
Ruim:
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 ;
}
}Bom:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}Ruim:
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" ) ;
}
}Bom:
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 volta ao topo
Não force o leitor do seu código a traduzir o que a variável significa. Explícito é melhor do que implícito .
Ruim:
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 ) ;
}Bom:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆ de volta ao topo
As cadeias mágicas são valores de string especificados diretamente no código do aplicativo que afetam o comportamento do aplicativo. Freqüentemente, essas seqüências acabam sendo duplicadas dentro do sistema e, como não podem ser atualizadas automaticamente usando ferramentas de refatoração, elas se tornam uma fonte comum de bugs quando as alterações são feitas em algumas strings, mas não outras.
Ruim
if ( userRole == "Admin" )
{
// logic in here
}Bom
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}Usando isso, só precisamos mudar no local centralizado e outros o adaptarão.
⬆ de volta ao topo
Se o nome da sua classe/objeto lhe informar algo, não repita isso em seu nome de variável.
Ruim:
public class Car
{
public string CarMake { get ; set ; }
public string CarModel { get ; set ; }
public string CarColor { get ; set ; }
//...
}Bom:
public class Car
{
public string Make { get ; set ; }
public string Model { get ; set ; }
public string Color { get ; set ; }
//...
}⬆ de volta ao topo
Ruim:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;Bom:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆ de volta ao topo
Ruim:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;Bom:
GetUser ( ) ;⬆ de volta ao topo
Vamos ler mais código do que jamais escreveremos. É importante que o código que escrevemos seja legível e pesquisável. Por não nomear variáveis que acabam sendo significativas para entender nosso programa, prejudicamos nossos leitores. Torne seus nomes pesquisáveis.
Ruim:
// 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 ( ) ) ;Bom:
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 volta ao topo
Ruim:
var data = new { Name = "John" , Age = 42 , PersonAccess = 4 } ;
// What the heck is 4 for?
if ( data . PersonAccess == 4 )
{
// do edit ...
}Bom:
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 volta ao topo
Ruim:
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 ) ;
}Bom:
Diminua a dependência do regex nomeando subpadrões.
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 volta ao topo
Não é bom:
Isso não é bom porque breweryName pode ser NULL .
Essa opinião é mais compreensível que a versão anterior, mas controla melhor o valor da variável.
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}Bom:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆ de volta ao topo
Uma função produz um efeito colateral se fizer algo diferente de obter um valor e retornar outro valor ou valores. Um efeito colateral pode estar escrevendo em um arquivo, modificando alguma variável global ou acidentalmente ligando todo o seu dinheiro a um estranho.
Agora, você precisa ter efeitos colaterais em um programa ocasionalmente. Como o exemplo anterior, pode ser necessário escrever em um arquivo. O que você quer fazer é centralizar onde você está fazendo isso. Não tenha várias funções e classes que escrevem em um arquivo específico. Tem um serviço que faz isso. Um e apenas um.
O ponto principal é evitar armadilhas comuns, como o estado de compartilhamento entre objetos sem estrutura, usando tipos de dados mutáveis que podem ser gravados por qualquer coisa e não centralizando onde seus efeitos colaterais ocorrem. Se você puder fazer isso, ficará mais feliz do que a grande maioria dos outros programadores.
Ruim:
// 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 McDermottBom:
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 volta ao topo
Ruim:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}Bom:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ de volta ao topo
Parece uma tarefa impossível. Ao ouvir isso primeiro, a maioria das pessoas diz: "Como devo fazer qualquer coisa sem uma declaração if ?" A resposta é que você pode usar o polimorfismo para alcançar a mesma tarefa em muitos casos. A segunda pergunta é geralmente: "Bem, isso é ótimo, mas por que eu gostaria de fazer isso?" A resposta é um conceito de código limpo anterior que aprendemos: uma função deve fazer apenas uma coisa. Quando você tem aulas e funções que possuem declarações if , está dizendo ao seu usuário que sua função faz mais de uma coisa. Lembre -se, basta fazer uma coisa.
Ruim:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}Bom:
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 volta ao topo
Ruim:
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" ) ) ;
}
}Bom:
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" ) ) ;
}
}⬆ de volta ao topo
Ruim:
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 ;
}Bom:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆ de volta ao topo
Uma bandeira indica que o método tem mais de uma responsabilidade. É melhor se o método tiver apenas uma única responsabilidade. Divida o método em dois se um parâmetro booleano adicionar várias responsabilidades ao método.
Ruim:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}Bom:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆ de volta ao topo
Globals poluidores é uma péssima prática em vários idiomas, porque você pode entrar em conflito com outra biblioteca e o usuário da sua API não seria o que eles tenham uma exceção na produção. Vamos pensar sobre um exemplo: e se você quisesse ter uma matriz de configuração. Você pode escrever uma função global como Config() , mas ela pode entrar em conflito com outra biblioteca que tentou fazer a mesma coisa.
Ruim:
public Dictionary < string , string > Config ( )
{
return new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ;
}Bom:
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 ;
}
} Carregue a configuração e crie a instância da classe Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; E agora você deve usar a instância de Configuration em seu aplicativo.
⬆ de volta ao topo
Singleton é um anti-padrão. Parafraseado de Brian Button:
Também há pensamentos muito bons de Misko Hevery sobre a raiz do problema.
Ruim:
class DBConnection
{
private static DBConnection _instance ;
private DBConnection ( )
{
// ...
}
public static GetInstance ( )
{
if ( _instance == null )
{
_instance = new DBConnection ( ) ;
}
return _instance ;
}
// ...
}
var singleton = DBConnection . GetInstance ( ) ;Bom:
class DBConnection
{
public DBConnection ( IOptions < DbConnectionOption > options )
{
// ...
}
// ...
} Crie a instância da classe DBConnection e configure -a com o padrão de opção.
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; E agora você deve usar a instância do DBConnection em seu aplicativo.
⬆ de volta ao topo
Limitar a quantidade de parâmetros de função é incrivelmente importante porque facilita o teste de sua função. Ter mais de três leads a uma explosão combinatória, onde você deve testar toneladas de casos diferentes a cada argumento separado.
Zero argumentos é o caso ideal. Um ou dois argumentos estão ok e três devem ser evitados. Qualquer coisa mais do que isso deve ser consolidado. Geralmente, se você tiver mais de dois argumentos, sua função está tentando fazer muito. Nos casos em que não está, na maioria das vezes, um objeto de nível superior será suficiente como argumento.
Ruim:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}Bom:
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 volta ao topo
Esta é de longe a regra mais importante na engenharia de software. Quando as funções fazem mais de uma coisa, elas são mais difíceis de compor, testar e raciocinar. Quando você pode isolar uma função com apenas uma ação, eles podem ser refaturados facilmente e seu código lerá muito mais limpo. Se você não tirar mais nada deste guia além disso, estará à frente de muitos desenvolvedores.
Ruim:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}Bom:
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 volta ao topo
Ruim:
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 ( ) ;Bom:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆ de volta ao topo
Ainda não terminou
Quando você tem mais de um nível de abstração, sua função geralmente está fazendo muito. A divisão de funções leva à reutilização e aos testes mais fáceis.
Ruim:
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...
}
}Ruim também:
Realizamos parte da funcionalidade, mas a função ParseBetterJSAlternative() ainda é muito complexa e não é testável.
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...
}
}Bom:
A melhor solução é mover as dependências da função 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 volta ao topo
Se uma função chamar outra, mantenha essas funções próximas verticalmente no arquivo de origem. Idealmente, mantenha o chamador logo acima do callee. Tendemos a ler o código de cima para baixo, como um jornal. Por causa disso, faça seu código ler dessa maneira.
Ruim:
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 ( ) ;Bom:
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 volta ao topo
Ruim:
if ( article . state == "published" )
{
// ...
}Bom:
if ( article . IsPublished ( ) )
{
// ...
}⬆ de volta ao topo
O código morto é tão ruim quanto o código duplicado. Não há razão para mantê -lo em sua base de código. Se não está sendo chamado, livre -se dele! Ainda estará seguro no histórico da sua versão, se você ainda precisar.
Ruim:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;Bom:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆ de volta ao topo
No C# / vb.net, você pode definir palavras -chave public , protected e private para métodos. Usando -o, você pode controlar a modificação das propriedades em um objeto.
set .Além disso, isso faz parte do princípio aberto/fechado, a partir de princípios de design orientados a objetos.
Ruim:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;Bom:
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 volta ao topo
Ruim:
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 DoeBom:
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 volta ao topo
Esse padrão é muito útil e comumente usado em muitas bibliotecas. Ele permite que seu código seja expressivo e menos detalhado. Por esse motivo, use o encadeamento do método e dê uma olhada em como o seu código será limpo.
Bom:
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 volta ao topo
Como declarado famosamente em padrões de design pela gangue de quatro, você deve preferir a composição ao longo da herança onde puder. Existem muitas boas razões para usar a herança e muitas boas razões para usar a composição.
O ponto principal para esta máxima é que, se sua mente instintivamente for para a herança, tente pensar se a composição poderá modelar melhor seu problema. Em alguns casos, pode.
Você pode estar se perguntando então: "Quando devo usar a herança?" Depende do seu problema em questão, mas esta é uma lista decente de quando a herança faz mais sentido do que a composição:
Ruim:
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 )
{
// ...
}
// ...
}Bom:
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 volta ao topo
Solid é o acrônimo mnemônico introduzido por Michael Feathers para os cinco primeiros princípios nomeados por Robert Martin, o que significou cinco princípios básicos de programação e design orientados a objetos.
Conforme declarado em código limpo, "nunca deve haver mais de um motivo para uma classe mudar". É tentador tocar uma aula com muitas funcionalidades, como quando você pode levar apenas uma mala no seu voo. O problema é que sua classe não será conceitualmente coesa e dará muitas razões para mudar. Minimizar a quantidade de vezes que você precisa para alterar uma classe é importante.
É importante porque, se houver muita funcionalidade em uma classe e você modificar uma parte dela, pode ser difícil entender como isso afetará outros módulos dependentes na sua base de código.
Ruim:
class UserSettings
{
private User User ;
public UserSettings ( User user )
{
User = user ;
}
public void ChangeSettings ( Settings settings )
{
if ( verifyCredentials ( ) )
{
// ...
}
}
private bool VerifyCredentials ( )
{
// ...
}
}Bom:
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 volta ao topo
Como afirmado por Bertrand Meyer, "Entidades de software (classes, módulos, funções etc.) devem estar abertas para extensão, mas fechadas para modificação". O que isso significa? Esse princípio afirma basicamente que você deve permitir que os usuários adicionem novas funcionalidades sem alterar o código existente.
Ruim:
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
}
}Bom:
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 volta ao topo
Este é um termo assustador para um conceito muito simples. É formalmente definido como "Se s for um subtipo de t, os objetos do tipo T podem ser substituídos por objetos do tipo S (ou seja, objetos do tipo S podem substituir objetos do tipo T) sem alterar nenhuma das propriedades desejáveis desse programa (correção, tarefa executada etc.)". Essa é uma definição ainda mais assustadora.
A melhor explicação para isso é que, se você tiver uma classe pai e uma classe infantil, a classe base e a classe infantil podem ser usadas de forma intercambiável sem obter resultados incorretos. Isso ainda pode ser confuso, então vamos dar uma olhada no exemplo clássico do Square-Rectangle. Matematicamente, um quadrado é um retângulo, mas se você o modelar usando o relacionamento "IS-A" por meio de herança, você rapidamente terá problemas.
Ruim:
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 ) ;Bom:
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 volta ao topo
O ISP afirma que "os clientes não devem ser forçados a depender de interfaces que não usam".
Um bom exemplo a ser analisado que demonstra esse princípio é para classes que requerem objetos de configurações grandes. Não exigir que os clientes configurem enormes quantidades de opções é benéfica, porque na maioria das vezes eles não precisam de todas as configurações. Torná -los opcionais ajuda a evitar ter uma "interface de gordura".
Ruim:
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
}
}Bom:
Nem todo trabalhador é um funcionário, mas todo funcionário é um trabalhador.
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 volta ao topo
Este princípio afirma duas coisas essenciais:
Isso pode ser difícil de entender no início, mas se você trabalhou com a estrutura do núcleo .NET/.NET, você viu uma implementação desse princípio na forma de injeção de dependência (DI). Embora eles não sejam conceitos idênticos, a DIP impede que os módulos de alto nível conheçam os detalhes de seus módulos de baixo nível e os configuram. Pode conseguir isso através de di. Um grande benefício disso é que ele reduz o acoplamento entre os módulos. O acoplamento é um padrão de desenvolvimento muito ruim, pois torna seu código difícil de refatorar.
Ruim:
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 ( ) ;
}
}Bom:
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 volta ao topo
Tente observar o princípio seco.
Faça o seu melhor para evitar o código duplicado. O código duplicado é ruim porque significa que há mais de um lugar para alterar algo se você precisar alterar alguma lógica.
Imagine se você correr um restaurante e acompanhar seu inventário: todos os seus tomates, cebolas, alho, especiarias etc. Se você tiver várias listas em que mantém isso, todos precisam ser atualizados quando você servir um prato com tomates neles. Se você tiver apenas uma lista, há apenas um lugar para atualizar!
Muitas vezes, você tem código duplicado porque tem duas ou mais coisas um pouco diferentes, que compartilham muito em comum, mas suas diferenças o forçam a ter duas ou mais funções separadas que fazem muitas das mesmas coisas. Remover o código duplicado significa criar uma abstração que possa lidar com esse conjunto de coisas diferentes com apenas uma função/módulo/classe.
Conseguir o certo a abstração é fundamental, é por isso que você deve seguir os princípios sólidos apresentados na seção de classes. As abstrações ruins podem ser piores que o código duplicado, então tenha cuidado! Dito isto, se você pode fazer uma boa abstração, faça -o! Não se repita, caso contrário, você estará atualizando vários lugares sempre que quiser mudar uma coisa.
Ruim:
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 ) ;
}
}Bom:
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 ) ;
}
}Muito bom:
É melhor usar uma versão compacta do código.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ de volta ao topo
O teste é mais importante que o envio. Se você não tiver testes ou um valor inadequado, toda vez que você enviará o código, você não tem certeza de que não quebrou nada. Decidir sobre o que constitui uma quantia adequada depende da sua equipe, mas ter 100% de cobertura (todas as declarações e ramos) é como você obtém confiança muito alta e tranquilidade do desenvolvedor. Isso significa que, além de ter uma ótima estrutura de teste, você também precisa usar uma boa ferramenta de cobertura.
Não há desculpa para não escrever testes. Há muitas estruturas de teste .NET boas, então encontre uma que sua equipe prefere. Quando você encontra um que funciona para sua equipe, pretende sempre escrever testes para cada novo recurso/módulo que você apresenta. Se o seu método preferido for o desenvolvimento orientado a testes (TDD), isso é ótimo, mas o ponto principal é apenas garantir que você esteja atingindo seus objetivos de cobertura antes de lançar qualquer recurso ou refatorar um existente.
Garante que seus testes sejam focados a laser e não testem as coisas diversas (não relacionadas), forças aaa patern usadas para tornar seus códigos mais limpos e legíveis.
Ruim:
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 ) ;
}
}Bom:
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 ) ;
}
}Soue https://www.codingblocks.net/podcast/how-to-write-amazing-unit-tests
⬆ de volta ao topo
Resumo das diretrizes de programação assíncrona
| Nome | Descrição | Exceções |
|---|---|---|
| Evite o vazio assíncrono | Preferem métodos de tarefas assíncronos sobre métodos de vazios assíncronos | Manipuladores de eventos |
| Assíncrono todo o caminho | Não misture o bloqueio e o código assíncrono | Método principal do console (c# <= 7.0) |
| Configure o contexto | Use ConfigureAwait(false) quando puder | Métodos que requerem contexto |
A maneira assíncreada de fazer as coisas
| Para fazer isso ... | Em vez disso ... | Use isso |
|---|---|---|
| Recuperar o resultado de uma tarefa de segundo plano | Task.Wait or Task.Result | await |
| Aguarde qualquer tarefa concluir | Task.WaitAny | await Task.WhenAny |
| Recuperar os resultados de várias tarefas | Task.WaitAll | await Task.WhenAll |
| Espere um período de tempo | Thread.Sleep | await Task.Delay |
Prática recomendada
O assíncrono/aguardo é o melhor para tarefas ligadas a IO (comunicação de rede, comunicação do banco de dados, solicitação HTTP etc.), mas não é bom aplicar em tarefas limitadas computacionais (atravessar a enorme lista, render uma imagem de abraço, etc.). Porque ele liberará o thread de retenção para o pool de threads e os núcleos disponíveis não envolverão para processar essas tarefas. Portanto, devemos evitar o uso de assíncronos/aguardando tarefas ligadas computacionais.
Para lidar com tarefas de limites computacionais, prefira usar Task.Factory.CreateNew com TaskCreationOptions é LongRunning . Ele iniciará um novo thread de segundo plano para processar uma tarefa limitada computacional pesada sem liberá -lo de volta ao pool de threads até que a tarefa seja concluída.
Conheça suas ferramentas
Há muito a aprender sobre assíncrona e aguardar, e é natural ficar um pouco desorientado. Aqui está uma referência rápida de soluções para problemas comuns.
Soluções para problemas comuns de assíncronos
| Problema | Solução |
|---|---|
| Crie uma tarefa para executar o código | Task.Run ou TaskFactory.StartNew (não o construtor Task ou Task.Start ) |
| Crie um invólucro de tarefas para uma operação ou evento | TaskFactory.FromAsync ou TaskCompletionSource<T> |
| Cancelamento de suporte | CancellationTokenSource e CancellationToken |
| Relatar progresso | IProgress<T> e Progress<T> |
| Lidar com fluxos de dados | TPL Dataflow ou extensões reativas |
| Sincronize o acesso a um recurso compartilhado | SemaphoreSlim |
| Inicializar de forma assíncrona um recurso | AsyncLazy<T> |
| Estruturas de produtores/consumidores prontos para assíncronos | TPL Dataflow ou AsyncCollection<T> |
Leia o documento de padrão assíncrono baseado em tarefas (TAP). É extremamente bem escrito e inclui orientações sobre o design da API e o uso adequado do assíncrono/aguardando (incluindo o cancelamento e o relatório de progresso).
Existem muitas novas técnicas de aguardar que devem ser usadas em vez das antigas técnicas de bloqueio. Se você tem algum desses exemplos antigos em seu novo código assíncrono, está fazendo errado (TM):
| Velho | Novo | Descrição |
|---|---|---|
task.Wait | await task | Espere/aguarde para uma tarefa concluir |
task.Result | await task | Obter o resultado de uma tarefa concluída |
Task.WaitAny | await Task.WhenAny | Espere/aguarde para que uma de uma coleção de tarefas seja concluída |
Task.WaitAll | await Task.WhenAll | Espere/aguarde para cada uma de uma coleção de tarefas para concluir |
Thread.Sleep | await Task.Delay | Espere/aguarde por um período de tempo |
Construtor Task | Task.Run ou TaskFactory.StartNew | Crie uma tarefa baseada em código |
Fonte https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
⬆ de volta ao topo
Erros jogados são uma coisa boa! Eles significam que o tempo de execução foi identificado com sucesso quando algo em seu programa deu errado e está informando que você sabe interrompendo a execução da função na pilha atual, matando o processo (no núcleo .NET/.NET) e notificando você no console com um rastreamento de pilha.
Se você precisar renovar uma exceção depois de pegá-lo, use apenas 'arremesso' usando isso, você salvará o rastreamento da pilha. Mas na má opção abaixo, você perdeu o rastreamento da pilha.
Ruim:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ex ;
}Bom:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ;
}⬆ de volta ao topo
Não fazer nada com um erro capturado não fornece a capacidade de consertar ou reagir ao referido erro. Jogando o erro não é muito melhor, muitas vezes, ele pode se perder em um mar de coisas impressas no console. Se você envolver qualquer bit de código em uma try/catch , significa que você acha que pode ocorrer um erro lá e, portanto, deve ter um plano ou criar um caminho de código, para quando ocorrer.
Ruim:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}Bom:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆ de volta ao topo
Se você precisar agir de acordo com o tipo de exceção, é melhor usar vários blocos de captura para manuseio de exceções.
Ruim:
try
{
// Do something..
}
catch ( Exception ex )
{
if ( ex is TaskCanceledException )
{
// Take action for TaskCanceledException
}
else if ( ex is TaskSchedulerException )
{
// Take action for TaskSchedulerException
}
}Bom:
try
{
// Do something..
}
catch ( TaskCanceledException ex )
{
// Take action for TaskCanceledException
}
catch ( TaskSchedulerException ex )
{
// Take action for TaskSchedulerException
}⬆ de volta ao topo
C# permite que a exceção seja renegado em um bloco de captura usando a palavra -chave throw . É uma péssima prática lançar uma exceção pega usando throw e; . Esta declaração redefine o rastreamento da pilha. Em vez disso, use throw; . Isso manterá o rastreamento da pilha e fornecerá uma visão mais profunda sobre a exceção. Outra opção é usar uma exceção personalizada. Simplesmente instancie uma nova exceção e defina sua propriedade de exceção interna à exceção capturada com o Throw new CustomException("some info", e); . Adicionar informações a uma exceção é uma boa prática, pois ajudará na depuração. No entanto, se o objetivo for registrar uma exceção, use throw; para passar o dinheiro para o chamador.
Ruim:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
logger . LogInfo ( ex ) ;
throw ex ;
}Bom:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw ;
}Bom:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw new CustomException ( error ) ;
}⬆ de volta ao topo
Ruim:
Tem muitos estilos de formatação de código no projeto. Por exemplo, o estilo de recuo é space e tab misturados no projeto.
Bom:
Defina e mantenha um estilo de código consistente em sua base de código com o uso de um arquivo .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 volta ao topo
Eles geralmente adicionam ruído. Deixe as funções e nomes de variáveis, juntamente com o indentação e a formatação adequadas, dê a estrutura visual ao seu código.
Ruim:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;Ruim:
#region Scope Model Instantiation
var model = {
menu : 'foo' ,
nav : 'bar'
} ;
#endregion
#region Action setup
void Actions ( ) {
// ...
} ;
#endregionBom:
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
void Actions ( )
{
// ...
} ;⬆ de volta ao topo
O controle de versão existe por um motivo. Deixe o código antigo em sua história.
Ruim:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Bom:
doStuff ( ) ;⬆ de volta ao topo
Lembre -se, use o controle da versão! Não há necessidade de código morto, código comentado e, especialmente, comentários do diário. Use git log para obter histórico!
Ruim:
/**
* 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 ;
}Bom:
public int Combine ( int a , int b )
{
return a + b ;
}⬆ de volta ao topo
Os comentários são um pedido de desculpas, não um requisito. O bom código se documenta principalmente .
Ruim:
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 ;
}
}Melhor, mas ainda ruim:
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 ;
}
} Se um comentário explicar o que o código está fazendo, provavelmente é um comentário inútil e pode ser implementado com uma variável ou função bem nomeada. O comentário no código anterior pode ser substituído por uma função chamada ConvertTo32bitInt , para que esse comentário ainda seja inútil. No entanto, seria difícil expressar por código por que o desenvolvedor escolheu o algoritmo de hash djb2 em vez de sha-1 ou outra função de hash. Nesse caso, um comentário é aceitável.
Bom:
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 volta ao topo
Obrigado a todas as pessoas que já contribuíram para o projeto de clean-code-dotnet
Ame nosso trabalho e ajude -nos a continuar nossas atividades? [Torne -se um patrocinador]
Torne -se um patrocinador e obtenha seu logotipo em nosso ReadMe no GitHub com um link para o seu site. [Torne -se um patrocinador]
Na medida em que possível, por lei, Thangchung renunciou a todos os direitos autorais e direitos relacionados ou vizinhos a este trabalho.