Se você gostou do projeto clean-code-dotnet ou se ele te ajudou de alguma maneira, considere dar uma estrela ⭐ para este repositório. Isto irá não somente fortalecer nossa comunidade .NET mas também melhorar as habilidades de código limpo para os desenvolvedores .NET ao redor do mundo. Muito obrigado 👍
Este projeto é baseado no projeto clean-code-dotnet de Thang Chung e você pode conferí-lo na integra clicando neste link. Você pode também ter mais informações no blog do autor ou mandando um Oi para ele no Twitter!
- Conceitos de Clean Code (Código Limpo) adaptado para .NET/.NET Core
- Conteúdo
- Introdução
- Clean Code .NET
- Outros Recursos de Clean Code
Software engineering principles, livro de Robert C. Martin's Clean Code, adaptado para .NET/.NET Core. Este documento não é um guia de estilos. Este documento é um guia de como produzir software com código legível, reusável e refatorável em .NET/.NET Core.
Nem todos os princípios aqui contidos devem ser rigorosamente seguidos, e muito menos estão universalmente acordados. Aqui você encontrará somente algumas diretrizes e nada mais, que foram codificadas ao longo de muitos anos de experiência coletiva pelos autores do Clean Code.
Inspirado nas listas clean-code-javascript e clean-code-php.
Evite usar nomes ruins
Um bom nome permite que o código possa ser utilizado por muitos desenvolvedores. O nome deve refletir o que faz e dar o contexto.
❌ Errado
int d;✔️ Correto
int diasAposModificacao;Evite nomes enganosos
Nomeie a variável para refletir para que ela é usada.
❌ Errado
var dadosBanco = db.ObterDados().ToList();✔️ Correto
var listaColaboradores = _colaboradorService.ObterColaboradores().ToList();Evite a notação húngara
A notação húngara reafirma o tipo que já está presente na declaração. Isso é inútil, pois os IDEs modernos já fazem isso.
❌ Errado
int iContador;
string strNomeCompleto;
DateTime dDataModificacao;✔️ Correto
int contador;
string nomeCompleto;
DateTime dataModificacao;A notação húngara também não deve ser usada em parâmetros.
❌ Errado
public bool VerificarLojaAberta(string pDia, int pTotal)
{
// alguma lógica
}✔️ Correto
public bool VerificarLojaAberta(string dia, int total)
{
// alguma lógica
}Use uma capitalização consistente
A capitalização diz muito sobre suas variáveis, funções, etc.
Essas regras são subjetivas, porém sua equipe pode escolher como eles querem utilizar.
A questão é: não importa o que vocês escolham, apenas sejam consistentes.
❌ Errado
const int DIAS_NA_SEMANA = 7;
const int diasNoMes = 30;
var musicas = new List<string> { 'Back In Black', 'Stairway to Heaven', 'Hey Jude' };
var Artistas = new List<string> { 'ACDC', 'Led Zeppelin', 'The Beatles' };
bool ApagarBancoDeDados() {}
bool Restaurar_BancoDeDados() {}
class animal {}
class Alpaca {}✔️ Correto
const int DiasNaSemana = 7;
const int DiasNoMes = 30;
var musicas = new List<string> { 'Back In Black', 'Stairway to Heaven', 'Hey Jude' };
var artistas = new List<string> { 'ACDC', 'Led Zeppelin', 'The Beatles' };
bool ApagarBancoDeDados() {}
bool RestaurarBancoDeDados() {}
class Animal {}
class Alpaca {}Use nomes pronunciáveis
Levará um bom tempo para investigar o significado das variáveis e funções quando elas não puderem ser pronunciadas.
❌ Errado
public class Colaborador
{
public Datetime dtinitrab { get; set; } // Que diabos é isso
public Datetime hrmod { get; set; } // Mesma coisa
}✔️ Correto
public class Employee
{
public Datetime DataInicioTrabalho { get; set; }
public Datetime HoraModificacao { get; set; }
}Use a notação camelCase
Use Notação Camelcase para variáveis e parâmetros no método.
❌ Errado
var telefonecolaborador;
public double CalcularSalario(int diastrabalhados, int horastrabalhadas)
{
// alguma lógica
}✔️ Correto
var telefoneColaborador;
public double CalcularSalario(int diasTrabalhados, int horasTrabalhadas)
{
// alguma lógica
}Use nomes que identificam o domínio
As pessoas que leem seu código também são programadores. Nomear as coisas corretamente ajudará todos a estarem na mesma página.
Não queremos perder tempo explicando a todos para que serve uma variável ou função.
✔️ Correto
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();
}Evite aninhar profundamente blocos condicionais, e retorne antes sempre que puder
Muitas instruções if/else podem dificultar o acompanhamento do código. Explícito é melhor que implícito
❌ Errado
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;
}
}✔️ Correto
public bool IsShopOpen(string day)
{
if (string.IsNullOrEmpty(day))
{
return false;
}
var openingDays = new[] { "friday", "saturday", "sunday" };
return openingDays.Any(d => d == day.ToLower());
}❌ Errado
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");
}
}✔️ Correto
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);
}Evite o mapeamento mental
Não force o leitor do seu código a traduzir o que a variável significa. Explícito é melhor que implícito.
❌ Errado
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);
}✔️ Correto
var locations = new[] { "Austin", "New York", "San Francisco" };
foreach (var location in locations)
{
DoStuff();
DoSomeOtherStuff();
// ...
// ...
// ...
Dispatch(location);
}Evite a string mágica
Strings mágicas são valores de string especificados diretamente no código do aplicativo que têm impacto no comportamento do aplicativo.
Freqüentemente, essas strings acabarão 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 alterações são feitas em algumas strings, mas não em outras.
❌ Errado
if (userRole == "Admin")
{
// logic in here
}✔️ Correto
const string ADMIN_ROLE = "Admin";
if (userRole == ADMIN_ROLE)
{
// logic in here
}Usando desta forma, teremos só que mudar no local centralizado e todos os outros pontos do sistema irão adaptar-se.
Não adicione contexto desnecessário
Se o nome da sua classe/objeto lhe disser algo, não repita isso no nome da sua variável.
❌ Errado
public class Car
{
public string CarMake { get; set; }
public string CarModel { get; set; }
public string CarColor { get; set; }
//...
}✔️ Correto
public class Car
{
public string Make { get; set; }
public string Model { get; set; }
public string Color { get; set; }
//...
}Use nomes significativos e pronunciáveis para as variáveis
❌ Errado
var ymdstr = DateTime.UtcNow.ToString("MMMM dd, yyyy");✔️ Correto
var currentDate = DateTime.UtcNow.ToString("MMMM dd, yyyy");Use o mesmo vocabulário para o mesmo tipo de variável
❌ Errado
GetUserInfo();
GetUserData();
GetUserRecord();
GetUserProfile();✔️ Correto
GetUser();Use nomes pesquisáveis (parte 1)
Leremos mais código do que escreveremos. É importante que o código que escrevemos seja legível e pesquisável.
Ao não nomear variáveis que acabam sendo significativas para a compreensão do nosso programa, prejudicamos nossos leitores.
Torne seus nomes pesquisáveis.
❌ Errado
// Para que serve o 'data'?
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());✔️ Correto
var person = new Person
{
Name = "John",
Age = 42
};
var stream2 = new MemoryStream();
var ser2 = new DataContractJsonSerializer(typeof(Person));
ser2.WriteObject(stream2, person);
stream2.Position = 0;
var sr2 = new StreamReader(stream2);
Console.Write("JSON form of Data object: ");
Console.WriteLine(sr2.ReadToEnd());Use nomes pesquisáveis (parte 2)
❌ Errado
var data = new { Name = "John", Age = 42, PersonAccess = 4};
// Para que serve o 4?
if (data.PersonAccess == 4)
{
// editar ...
}✔️ Correto
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)
{
// editar ...
}Use variáveis explicativas
❌ Errado
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);
}✔️ Correto
Diminua a dependência de 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);
}Use argumentos padrão em vez de curto-circuito ou condicionais
❌ Errado
Isso não é bom porque breweryName pode ser de fato NULL.
public void CreateMicrobrewery(string name = null)
{
var breweryName = !string.IsNullOrEmpty(name) ? name : "Hipster Brew Co.";
// ...
}✔️ Correto
public void CreateMicrobrewery(string breweryName = "Hipster Brew Co.")
{
// ...
}Evite efeitos colaterais
Uma função produz um efeito colateral se fizer qualquer coisa além de receber um valor e retornar outro valor ou valores. Um efeito colateral pode ser gravar em um arquivo, modificar alguma variável global ou transferir acidentalmente todo o seu dinheiro para um estranho.
Agora, você precisa ter efeitos colaterais em um programa ocasionalmente. Como no exemplo anterior, pode ser necessário gravar em um arquivo. O que você quer fazer é centralizar onde você está fazendo isso. Não tenha várias funções e classes que gravem em um arquivo específico. Tenha um serviço que faça isso. Um e somente um.
O ponto principal é evitar armadilhas comuns, como compartilhar estado entre objetos sem qualquer estrutura, usar tipos de dados mutáveis que podem ser gravados por qualquer coisa e não centralizar onde ocorrem os efeitos colaterais. Se você puder fazer isso, você será mais feliz do que a grande maioria dos outros programadores.
❌ Errado
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
var name = "Ryan McDermott";
public void SplitAndEnrichFullName()
{
var temp = name.Split(" ");
name = $"His first name is {temp[0]}, and his last name is {temp[1]}"; // side effect
}
SplitAndEnrichFullName();
Console.WriteLine(name); // His first name is Ryan, and his last name is McDermott✔️ Correto
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 McDermottEvite condicionais negativas
❌ Errado
public bool IsDOMNodeNotPresent(string node)
{
// ...
}
if (!IsDOMNodeNotPresent(node))
{
// ...
}✔️ Correto
public bool IsDOMNodePresent(string node)
{
// ...
}
if (IsDOMNodePresent(node))
{
// ...
}Evite condicionais usando polimorfismo
Esta parece ser uma tarefa impossível. Ao ouvir isso pela primeira vez, a maioria das pessoas diz: "como posso fazer algo sem uma declaração if?".
A resposta é que você pode usar o polimorfismo para realizar a mesma tarefa em muitos casos.
A segunda pergunta geralmente é: "bem, isso é ótimo, mas por que eu iria querer fazer isso?"
A resposta é um conceito anterior de código limpo que aprendemos: uma função só deve fazer uma Coisa. Quando você tem classes e funções que possuem instruções if, você está dizendo ao usuário que sua função faz mais de uma coisa.
Lembre-se, faça apenas uma coisa.
❌ Errado
class Airplane
{
// ...
public double GetCruisingAltitude()
{
switch (_type)
{
case '777':
return GetMaxAltitude() - GetPassengerCount();
case 'Air Force One':
return GetMaxAltitude();
case 'Cessna':
return GetMaxAltitude() - GetFuelExpenditure();
}
}
}✔️ Correto
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();
}
}Evite a verificação de tipos (parte 1)
❌ Errado
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"));
}
}✔️ Correto
public Path TravelToTexas(Traveler vehicle)
{
vehicle.TravelTo(new Location("texas"));
}✔️ Correto
// 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"));
}
}Evite a verificação de tipos (parte 2)
❌ Errado
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;
}✔️ Correto
public int Combine(int val1, int val2)
{
return val1 + val2;
}Evite flags nos parâmetros do método
Uma flag indica que o método tem mais de uma responsabilidade. É melhor que o método tenha apenas uma única responsabilidade.
Divida o método em dois se um parâmetro booleano adicionar múltiplas responsabilidades ao método.
❌ Errado
public void CreateFile(string name, bool temp = false)
{
if (temp)
{
Touch("./temp/" + name);
}
else
{
Touch(name);
}
}✔️ Correto
public void CreateFile(string name)
{
Touch(name);
}
public void CreateTempFile(string name)
{
Touch("./temp/" + name);
}Não escreva em funções globais
Poluir globais é uma prática ruim em muitas linguagens porque você poderia entrar em conflito com outra biblioteca e o usuário de sua API não saberia até obter uma exceção na produção. Vamos pensar em um exemplo: e se você quisesse ter um array de configuração.
Você poderia escrever uma função global como Config(), mas ela poderia entrar em conflito com outra biblioteca que tentasse fazer a mesma coisa.
❌ Errado
public Dictionary<string, string> Config()
{
return new Dictionary<string,string>(){
["foo"] = "bar"
};
}✔️ Correto
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;
}
}Carregar configuração e criar instância da classe Configuration
var configuration = new Configuration(new Dictionary<string, string>() {
["foo"] = "bar"
});E agora você deve usar a instância de Configuração em sua aplicação.
var fooValue = configuration.Get("foo");Não use o padrão Singleton
Singleton é um anti-pattern e não deve ser usado. Parafraseado por Brian Button:
- Eles geralmente são usados como uma instância global e por que isso é tão ruim? Porque você oculta as dependências da sua aplicação no seu código, em vez de expô-las através das interfaces. Tornar algo global para evitar distribuí-lo é um Code Smell.
- Eles violam o princípio de responsabilidade única: em virtude do fato de que eles controlam sua própria criação e ciclo de vida.
- Eles inerentemente fazem com que o código seja fortemente acoplado. Isso torna bastante difícil falsea-los durante os testes na maioria dos casos.
- Eles carregam o estado durante toda a vida útil da aplicação. Isso pode ocasionar problemas nos testes unitários porque um teste pode considerar o estado do objeto resultante de outro teste anterior, e ocasionar um resultado de falso/positivo. Cada teste unitário deve ser independente um do outro.
Também há considerações muito boas de Misko Hevery sobre a raiz do problema.
❌ Errado
class DBConnection
{
private static DBConnection _instance;
private DBConnection()
{
// ...
}
public static GetInstance()
{
if (_instance == null)
{
_instance = new DBConnection();
}
return _instance;
}
// ...
}
var singleton = DBConnection.GetInstance();✔️ Correto
class DBConnection
{
public DBConnection(IOptions<DbConnectionOption> options)
{
// ...
}
// ...
}Crie uma instância da classe DBConnection e configure-a com o Option pattern.
var options = <resolve from IOC>;
var connection = new DBConnection(options);E agora você pode usar a instância de DBConnection em sua aplicação.
Argumentos de função (2 ou menos, idealmente)
Limitar a quantidade de parâmetros de função é extremamente importante porque facilita o teste de sua função. Ter mais de três leva a uma explosão combinatória onde você terá que testar vários casos diferentes com cada argumento separado.
Zero argumentos é o caso ideal. Um ou dois argumentos são aceitáveis e três devem ser evitados. Qualquer coisa além disso deve ser consolidada. Normalmente, se você tiver mais de dois argumentos, sua função está tentando fazer muita coisa. Nos casos em que não é, na maioria das vezes um objeto de nível superior será suficiente como argumento.
:x: Errado
public void CreateMenu(string title, string body, string buttonText, bool cancellable)
{
// ...
}✔️ Correto
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)
{
// ...
}Funções devem fazer somente uma coisa
Esta é de longe a regra mais importante na engenharia de software.
Quando as funções fazem mais de uma coisa, são mais difíceis de compor, testar e raciocinar. Quando você pode isolar uma função para apenas uma ação, elas podem ser refatoradas facilmente e seu código será lido com mais facilidade.
Se você não tirar nada deste guia além deste tópico, estará à frente de muitos desenvolvedores.
❌ Errado
public void SendEmailToListOfClients(string[] clients)
{
foreach (var client in clients)
{
var clientRecord = db.Find(client);
if (clientRecord.IsActive())
{
Email(client);
}
}
}✔️ Correto
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");
}Os nomes das funções devem dizer o que fazem
❌ Errado
public class Email
{
//...
public void Handle()
{
SendMail(this._to, this._subject, this._body);
}
}
var message = new Email(...);
// O que é isto? Um manipulador para a mensagem? Nós estamos escrevendo para um arquivo agora?
message.Handle();✔️ Correto
public class Email
{
//...
public void Send()
{
SendMail(this._to, this._subject, this._body);
}
}
var message = new Email(...);
// Claro e óbvio
message.Send();As funções devem ter apenas um nível de abstração
Carece de mais explicação
Quando você tem mais de um nível de abstração, sua função geralmente está fazendo muita coisa. A divisão de funções leva à reutilização e a testes mais fáceis.
❌ Errado
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...
}
}❌ Errado também
Realizamos algumas funcionalidades, mas a função ParseBetterJSAlternative() ainda é muito complexa e não pode ser testada.
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...
}
}✔️ Correto
A melhor solução é remover 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...
}
}
}Chamadores e receptores de funções devem estar próximos
Se uma função chamar outra, mantenha essas funções fechadas verticalmente no arquivo de origem.
O ideal é manter o chamador logo acima do receptor. Tendemos a ler o código de cima para baixo, como um jornal. Por causa disso, faça seu código ser lido dessa maneira.
❌ Errado
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();✔️ Correto
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();Encapsule condicionais
❌ Errado
if (article.state == "published")
{
// ...
}✔️ Correto
if (article.IsPublished())
{
// ...
}Remova o código morto
Código morto é tão ruim quanto código duplicado. Não há razão para mantê-lo em sua base de código.
Se não estiver sendo chamado, livre-se dele! Ainda estará seguro em seu histórico de versões se você ainda precisar dele.
❌ Errado
public void OldRequestModule(string url)
{
// ...
}
public void NewRequestModule(string url)
{
// ...
}
var request = NewRequestModule(requestUrl);
InventoryTracker("apples", request, "www.inventory-awesome.io");✔️ Correto
public void RequestModule(string url)
{
// ...
}
var request = RequestModule(requestUrl);
InventoryTracker("apples", request, "www.inventory-awesome.io");Use getters e setters
Em C#/VB.NET você pode definir palavras-chave public, protected e private para métodos.
Usando isto, você pode controlar a modificação de propriedades em um objeto.
- Quando você quiser fazer mais além de obter uma propriedade de objeto, não precisará procurar e alterar todos os acessadores em sua base de código.
- Simplifica a adição de validação ao fazer um
set. - Encapsula a representação interna.
- Fácil de adicionar registro e tratamento de erros ao obter ou definir valores.
- Herdando esta classe, você pode substituir a funcionalidade padrão.
- Você pode carregar sobre demanda as propriedades do seu objeto.
Além disso, isso faz parte do princípio Aberto/Fechado, dos princípios de design orientado a objetos.
❌ Errado
class BankAccount
{
public double Balance = 1000;
}
var bankAccount = new BankAccount();
// Fake buy shoes...
bankAccount.Balance -= 100;✔️ Correto
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;Faça com que os objetos tenham membros privados/protegidos
❌ Errado
class Employee
{
public string Name { get; set; }
public Employee(string name)
{
Name = name;
}
}
var employee = new Employee("John Doe");
Console.WriteLine(employee.Name); // Employee name: John Doe✔️ Correto
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 DoeUse encadeamento de métodos
Este 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 de métodos e veja como seu código ficará limpo.
✔️ Correto
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();
}Prefira composição à herança
Conforme declarado em Design Patterns pela Gang of Four (GOF), você deve preferir a composição à herança sempre que possível. Existem muitos bons motivos para usar herança e muitos bons motivos para usar composição.
O ponto principal dessa máxima é que se sua mente busca instintivamente a herança, tente pensar se a composição poderia modelar melhor seu problema. Em alguns casos, pode.
Você deve estar se perguntando: "quando devo usar herança?" Isto 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:
- Sua herança representa um relacionamento "é um" e não um relacionamento "tem um" (Humano->Animal vs. Usuário->UserDetails).
- Você pode reutilizar o código das classes base (os humanos podem se mover como todos os animais).
- Você deseja fazer alterações globais nas classes derivadas alterando uma classe base (Alterar o gasto calórico de todos os animais quando eles se movem).
❌ Errado
class Employee
{
private string Name { get; set; }
private string Email { get; set; }
public Employee(string name, string email)
{
Name = name;
Email = email;
}
// ...
}
// Ruim porque os funcionários (Employee) "possuem" dados fiscais (EmployeeTaxData).
// EmployeeTaxData não é um tipo de Employee
class EmployeeTaxData : Employee
{
private string Name { get; }
private string Email { get; }
public EmployeeTaxData(string name, string email, string ssn, string salary)
{
// ...
}
// ...
}✔️ Correto
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);
}
// ...
}O que é SOLID?
SOLID é o acrônimo mnemônico (em inglês) introduzido por Michael Feathers para os primeiros cinco princípios nomeados por Robert Martin, que significavam cinco princípios básicos de programação e design orientados a objetos.
- S: Single Responsibility Principle (SRP) ou Princípio da Responsabilidade Única
- O: Open/Closed Principle (OCP) ou Princípio Aberto/Fechado
- L: Liskov Substitution Principle (LSP) ou Princípio da Substituição de Liskov
- I: Interface Segregation Principle (ISP) ou Princípio da Segregação de Interfaces
- D: Dependency Inversion Principle (DIP) ou Princípio da Inversão de Dependência
Single Responsibility Principle (SRP)
Princípio da Responsabilidade Única
Conforme declarado no Clean Code, "Nunca deve haver mais de um motivo para a mudança de uma classe". É tentador lotar uma aula com muitas funcionalidades, como quando você só pode levar uma mala no voo. O problema com isso é que sua classe não será conceitualmente coesa e isso lhe dará muitos motivos para mudar. É importante minimizar a quantidade de vezes que você precisa mudar de classe.
É 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 em sua base de código.
❌ Errado
class UserSettings
{
private User User;
public UserSettings(User user)
{
User = user;
}
public void ChangeSettings(Settings settings)
{
if (verifyCredentials())
{
// ...
}
}
private bool VerifyCredentials()
{
// ...
}
}✔️ Correto
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())
{
// ...
}
}
}Open/Closed Principle (OCP)
Princípio Aberto/Fechado
Conforme afirma Bertrand Meyer, “entidades de software (classes, módulos, funções, etc.) devem ser abertas para extensão, mas fechadas para modificação”. O que isso significa? Este princípio basicamente afirma que você deve permitir que os usuários adicionem novas funcionalidades sem alterar o código existente.
❌ Errado
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
}
}✔️ Correto
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);
}
}Liskov Substitution Principle (LSP)
Princípio da Substituição de Liskov
Este é um termo assustador para um conceito muito simples. É formalmente definido como "Se S é um subtipo de T, então 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 filha, então a classe base e a classe filha 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 Quadrado-Retângulo. Matematicamente, um quadrado é um retângulo, mas se você modelá-lo usando o relacionamento “é-um” via herança, você rapidamente ter um problema.
❌ Errado
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);✔️ Correto
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);Interface Segregation Principle (ISP)
Princípio da Segregação de Interfaces
O ISP afirma que “os clientes não devem ser forçados a depender de interfaces que não utilizam”.
Um bom exemplo que demonstra esse princípio é para classes que exigem objetos de configurações grandes. Não exigir que os clientes configurem grandes quantidades de opções é benéfico, porque na maioria das vezes eles não precisarão de todas as configurações. Torná-los opcionais ajuda a evitar uma "interface gorda".
❌ Errado
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
}
}✔️ Correto
Not every worker is an employee, but every employee is an worker.
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
}
}Dependency Inversion Principle (DIP)
Princípio de Inversão de Dependência
Isso pode ser difícil de entender no início, mas se você trabalhou com a estrutura .NET/.NET Core, viu uma implementação desse princípio na forma de Injeção de Dependência (DI). Embora não sejam conceitos idênticos, o DIP impede que os módulos de alto nível conheçam os detalhes de seus módulos de baixo nível e os configurem. Isso pode ser feito por meio do DI. Um grande benefício disso é que reduz o acoplamento entre os módulos. O acoplamento é um padrão de desenvolvimento muito ruim porque dificulta a refatoração do seu código.
❌ Errado
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();
}
}✔️ Correto
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();
}
}
}DRY - Don’t repeat yourself (Não repita você mesmo)
Tente observar o princípio DRY.
Faça o seu melhor para evitar código duplicado. 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ê administra um restaurante e monitora seu estoque: todos os seus tomates, cebolas, alho, temperos, etc. Se você tiver várias listas nas quais mantém isso, todas deverão ser atualizadas quando você servir um prato com tomates neles. Se você tiver apenas uma lista, só há um lugar para atualizar!
Muitas vezes você tem código duplicado porque tem duas ou mais coisas ligeiramente diferentes, que têm muito em comum, mas suas diferenças forçam você a ter duas ou mais funções separadas que fazem muitas das mesmas coisas. Remover 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.
Acertar a abstração é fundamental, por isso você deve seguir os princípios SOLID descritos na seção Classes. Abstrações ruins podem ser piores que código duplicado, então tome cuidado! Dito isto, se você consegue fazer uma boa abstração, faça-a! Não se repita, caso contrário você se verá atualizando vários lugares sempre que quiser mudar alguma coisa.
❌ Errado
public List<EmployeeData> ShowDeveloperList(Developers developers)
{
List<EmployeeData> listOfEmployeeData = new List<EmployeeData>();
foreach (var developers in developer)
{
var expectedSalary = developer.CalculateExpectedSalary();
var experience = developer.GetExperience();
var githubLink = developer.GetGithubLink();
var employeeData = new EmployeeData(
expectedSalary,
experience,
githubLink
);
listOfEmployeeData.Add(employeeData);
}
}
public List<ManagerData> ShowManagerList(Manager managers)
{
List<ManagerData> listOfManagerData = new List<ManagerData>();
foreach (var manager in managers)
{
var expectedSalary = manager.CalculateExpectedSalary();
var experience = manager.GetExperience();
var githubLink = manager.GetGithubLink();
var managerData = new ManagerData(
expectedSalary,
experience,
githubLink
);
listOfManagerData.Add(managerData);
}
}✔️ Correto
public List<EmployeeData> ShowList(Employee employees)
{
List<EmployeeData> listOfEmployeeData = new List<EmployeeData>();
foreach (var employee in employees)
{
var expectedSalary = employees.CalculateExpectedSalary();
var experience = employees.GetExperience();
var githubLink = employees.GetGithubLink();
var employeeData = new EmployeeData(
expectedSalary,
experience,
githubLink
);
listOfEmployeeData.Add(employeeData);
}
}✔️ Melhor ainda
É melhor usar uma versão compacta do código.
public List<EmployeeData> ShowList(Employee employees)
{
List<EmployeeData> listOfEmployeeData = new List<EmployeeData>();
foreach (var employee in employees)
{
listOfEmployeeData.Add(new EmployeeData(
employee.CalculateExpectedSalary(),
employee.GetExperience(),
employee.GetGithubLink()
));
}
}Conceito básico de teste
Teste é mais importante do que o envio do código. Se você não tiver testes ou tiver em quantidade inadequada, toda vez que você enviar o código, você não terá certeza de que não quebrou nada. A decisão sobre o que constitui um valor adequado cabe à sua equipe, mas ter 100% de cobertura (todos os statements e branches) é a maneira que você consegue ter uma confiança muito alta e também dar tranquilidade para o desenvolvedor. Isso significa que além de ter uma ótima estrutura de testes, você também precisa usar uma boa ferramenta de cobertura.
Não há desculpa para não escrever testes. Existem bons frameworks de teste em .NET, encontre uma que sua equipe prefira. Quando você encontrar um que funcione para sua equipe, tente sempre escrever testes para cada novo recurso/módulo introduzido.
Você pode optar também pelo método do Test Driven Development (TDD), que é um bom método para escrever os testes antes mesmo de escrever o código, porém o ponto principal é apenas ter certeza de que você está atingindo seus objetivos de cobertura antes de lançar qualquer recurso ou refatorar um existente.
Conceito de teste por únidade
Certifique que seus testes sejam focados em uma única coisa e não esteja testando coisas diversas (não relacionadas), force o padrão AAA para tornar seus códigos mais limpos e legíveis.
❌ Errado
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);
}
}✔️ Correto
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);
}
}Fonte: https://www.codingblocks.net/podcast/how-to-write-amazing-unit-tests
Use Async/Await
Resumo das diretrizes de programação assíncrona
| Nome | Descrição | Exceções |
|---|---|---|
Evite async void |
Prefira async Task em vez de async void |
Eventos (Event handlers) |
| Async por todo o caminho | Não misture código sincrono (bloqueador) e assíncrono | método Console main (C# <= 7.0) |
| Configure o contexto | Use ConfigureAwait(false) sempre que puder |
Métodos que requerem contexto |
O jeito assíncrono de fazer as coisas
| Para fazer isso ... | Ao invés disso ... | Use isto |
|---|---|---|
| Recuperar o resultado de uma tarefa em segundo plano | Task.Wait ou Task.Result |
await |
| Aguardar a conclusão de qualquer tarefa | Task.WaitAny |
await Task.WhenAny |
| Recuperar os resultados de múltiplas tarefas | Task.WaitAll |
await Task.WhenAll |
| Esperar por um período de tempo | Thread.Sleep |
await Task.Delay |
Boas práticas
O Async/Await é muito bom para tarefas vinculadas a IO (comunicação de rede, comunicação de banco de dados, solicitação http, etc.), mas não é bom para aplicar em tarefas vinculadas computacionalmente (atravessar em uma lista enorme, renderizar uma imagem enorme, etc.) . Porque isso liberará uma thread que está esperando em uma thread pool a CPU/núcleos disponíveis não envolverão o processamento dessas tarefas. Portanto, devemos evitar o uso de Async/Await para tarefas computacionais vinculadas.
Para lidar com tarefas computacionais, prefira usar Task.Factory.CreateNew com TaskCreationOptions é LongRunning. Ele iniciará uma nova thread em segundo plano para processar uma tarefa computacional pesada sem liberá-la de volta ao pool de threads até que a tarefa seja concluída.
Conheça suas ferramentas
Há muito o que aprender sobre async e await, e é natural ficar um pouco desorientado. Aqui está uma referência rápida de soluções para problemas comuns.
Soluções para problemas assíncronos comuns
| Problema | Solução |
|---|---|
| Criar uma tarefa para executar código | Task.Run ou TaskFactory.StartNew (não use o construtor Task ou Task.Start) |
| Criar um wrapper de tarefa para uma operação ou evento | TaskFactory.FromAsync ou TaskCompletionSource<T> |
| Suporte de cancelamento | CancellationTokenSource e CancellationToken |
| Reportar progresso | IProgress<T> e Progress<T> |
| Lidar com fluxos de dados | TPL Dataflow ou Reactive Extensions |
| Sincronize o acesso a um recurso compartilhado | SemaphoreSlim |
| Inicializar um recurso de forma assíncrona | AsyncLazy<T> |
| Estruturas de produtor/consumidor prontas para assíncrono | TPL Dataflow or AsyncCollection<T> |
Leia o documento Padrão Assíncrono Baseado em Tarefas (TAP). Ele é extremamente bem escrito e inclui orientações sobre design de API e o uso adequado de async/await (incluindo cancelamento e relatórios de progresso).
Existem muitas técnicas novas de espera que devem ser usadas em vez das antigas técnicas de bloqueio. Se você tiver algum desses exemplos antigos em seu novo código assíncrono, você está fazendo isso errado(TM):
| Antigo | Novo | Descrição |
|---|---|---|
task.Wait |
await task |
Aguarde a conclusão de uma tarefa |
task.Result |
await task |
Obtenha o resultado de uma tarefa concluída |
Task.WaitAny |
await Task.WhenAny |
Aguarde a conclusão de uma de uma coleção de tarefas |
Task.WaitAll |
await Task.WhenAll |
Aguarde a conclusão de cada uma de uma coleção de tarefas |
Thread.Sleep |
await Task.Delay |
Aguarde por um período de tempo |
Task construtor |
Task.Run or TaskFactory.StartNew |
Crie uma tarefa baseada em código |
Fonte https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
Conceito básico de tratamento de erros
Lançar erros é uma coisa boa! Significa que o tempo de execução identificou com sucesso quando algo deu errado em seu programa e está lhe informando interrompendo a execução da função na pilha atual, encerrando todo o processo (no .NET/.NET Core) e notificando você no console com um rastreamento de pilha.
Não use 'throw ex' no bloco catch
Se você precisar lançar novamente uma exceção depois de capturá-la, use apenas 'throw'. Ao usar isso, você preservará o rastreamento de pilha.
Se você fizer como na opção errada abaixo, você perderá o rastreamento de pilha.
❌ Errado
try
{
// Do something..
}
catch (Exception ex)
{
// Any action something like roll-back or logging etc.
throw ex;
}✔️ Correto
try
{
// Do something..
}
catch (Exception ex)
{
// Any action something like roll-back or logging etc.
throw;
}Não ignore os erros capturados
Não fazer nada com um erro detectado não lhe dá a capacidade de corrigir ou reagir a esse erro. Lançar o erro não é muito melhor, pois muitas vezes ele pode se perder em um mar de coisas impressas no console. Se você agrupar qualquer pedaço de código em um try/catch significa que você acha que um erro pode ocorrer ali e, portanto, você deve ter um plano, ou criar um caminho de código, para quando isso ocorrer.
❌ Errado
try
{
FunctionThatMightThrow();
}
catch (Exception ex)
{
// silent exception
}✔️ Correto
try
{
FunctionThatMightThrow();
}
catch (Exception error)
{
NotifyUserOfError(error);
// Another option
ReportErrorToService(error);
}Use vários blocos catch em vez de condições if.
Se você precisar tomar medidas de acordo com o tipo de exceção, é melhor você usar vários blocos catch para tratamento de exceções.
❌ Errado
try
{
// Do something..
}
catch (Exception ex)
{
if (ex is TaskCanceledException)
{
// Take action for TaskCanceledException
}
else if (ex is TaskSchedulerException)
{
// Take action for TaskSchedulerException
}
}✔️ Correto
try
{
// Do something..
}
catch (TaskCanceledException ex)
{
// Take action for TaskCanceledException
}
catch (TaskSchedulerException ex)
{
// Take action for TaskSchedulerException
}Mantenha o rastreamento da pilha de exceções ao relançar exceções
C# permite que a exceção seja lançada novamente em um bloco catch usando a palavra-chave throw. É uma má prática lançar uma exceção capturada usando throw ex;. Esta instrução redefine o rastreamento de pilha. Em vez disso, use throw;. Isso manterá o rastreamento de 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 para a exceção capturada com throw new CustomException("some info", ex);.
Adicionar informações a uma exceção é uma boa prática, pois ajudará na depuração. No entanto, se o objetivo é registrar uma exceção, use throw; para passar a responsabilidade para o chamador.
❌ Errado
try
{
FunctionThatMightThrow();
}
catch (Exception ex)
{
logger.LogInfo(ex);
throw ex;
}✔️ Correto
try
{
FunctionThatMightThrow();
}
catch (Exception error)
{
logger.LogInfo(error);
throw;
}✔️ Correto
try
{
FunctionThatMightThrow();
}
catch (Exception error)
{
logger.LogInfo(error);
throw new CustomException(error);
}Use exceção contextualizada em vez do objeto 'Exception'
Sempre que possível, lance exceções contextualizadas em vez de usar a exceção padrão.
❌ Errado
public double Divide(double dividend, double divider) {
if (divider == 0)
{
throw new Exception("Cannot be divided by zero");
}
return dividend / divider;
}✔️ Correto
public double Divide(double dividend, double divider) {
if (divider == 0)
{
throw new InvalidOperationException("Cannot be divided by zero");
}
return dividend / divider;
}Use o arquivo .editorconfig
❌ Errado
Possuir muitos estilos de formatação de código no projeto. Por exemplo, o estilo de recuo é space e tab misturados no projeto.
✔️ Correto
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
# internal 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 = 2Evite marcadores posicionais
Eles geralmente apenas adicionam ruído. Deixe que as funções e os nomes das variáveis, juntamente com o recuo e a formatação adequados, forneçam a estrutura visual ao seu código.
❌ Errado
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new[]
{
menu: 'foo',
nav: 'bar'
};
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions()
{
// ...
};❌ Errado
#region Scope Model Instantiation
var model = {
menu: 'foo',
nav: 'bar'
};
#endregion
#region Action setup
void Actions() {
// ...
};
#endregion✔️ Correto
var model = new[]
{
menu: 'foo',
nav: 'bar'
};
void Actions()
{
// ...
};Não deixe código comentado em sua base de código
O controle de versão existe por um motivo. Deixe o código antigo em seu histórico.
❌ Errado
doStuff();
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();✔️ Correto
doStuff();Não tenho histórico de comentários
Lembre-se, use o controle de versão! Não há necessidade de código morto, código comentado e, especialmente, histórico de comentários.
Use git log para obter o histórico!
❌ Errado
/**
* 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;
}✔️ Correto
public int Combine(int a,int b)
{
return a + b;
}Comente apenas coisas que tenham complexidade de lógica de negócios
Os comentários são um pedido de desculpas, não um requisito. Um bom código principalmente documenta a si mesmo.
❌ Errado
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 incorreto
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 explica 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 poderia ser substituído por uma função chamada ConvertTo32bitInt, portanto este comentário ainda é inútil.
No entanto, seria difícil expressar por código POR QUE o desenvolvedor escolheu o algoritmo hash djb2 em vez de sha-1 ou outra função hash. Nesse caso, um comentário é aceitável.
✔️ Correto
public int Hash(string data)
{
var hash = 0;
var length = data.length;
for (var i = 0; i < length; i++)
{
var character = data[i];
// use of djb2 hash algorithm as it has a good compromise
// between speed and low collision with a very simple implementation
hash = ((hash << 5) - hash) + character;
hash = ConvertTo32BitInt(hash);
}
return hash;
}
private int ConvertTo32BitInt(int value)
{
return value & value;
}- clean-code-javascript - Conceitos Clean Code adaptado para JavaScript
- clean-code-php - Conceitos Clean Code adaptado para PHP
- clean-code-ruby - Conceitos Clean Code adaptado para Ruby
- clean-code-python - Conceitos Clean Code adaptado para Python
- clean-code-typescript - Conceitos Clean Code adaptado para TypeScript
- clean-go-article - Conceitos Clean Code adaptado para Golang e um examplo de como aplicar clean code in Golang
- clean-abap - Conceitos Clean Code adaptado para ABAP
- programming-principles - Visão geral categorizada de princípios e padrões de programação
- Elixir-Code-Smells - Catálogo de code smells específicos do Elixir
- awesome-clean-code - Princípios de design, artigos em destaque, tutoriais, vídeos, exemplos de código, blogs e livros
- Google Styleguides - Este projeto contém o Guia de Estilo C++, Guia de Estilo Swift, Guia de Estilo Objective-C, Guia de Estilo Java, Guia de Estilo Python, Guia de Estilo R, Guia de Estilo Shell, Guia de Estilo HTML/CSS, Guia de Estilo JavaScript, Guia de Estilo AngularJS, Common Lisp Guia de estilo e guia de estilo Vimscript
- Django Styleguide - Guia de estilo Django usado em projetos HackSoft
- codemaid - Extensão de código aberto do Visual Studio para limpar e simplificar nossa codificação C#, C++, F#, VB, PHP, PowerShell, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript e TypeScript
- Sharpen - Extensão do Visual Studio que introduz de forma inteligente novos recursos C# em sua base de código existente
- tslint-clean-code - Regras TSLint para aplicar Código Limpo
- AspNetCoreDiagnosticScenarios - Exemplos de broken patterns em aplicativos ASP.NET Core
- Clean Code - Um resumo do livro Código limpo: habilidades práticas do Agile software
- Clean Architecture - Um resumo do livro Arquitetura limpa: o guia do artesão para estrutura e design de software
- Modern JavaScript Cheatsheet - Cheatsheet para o conhecimento de JavaScript que você encontrará frequentemente em projetos modernos
- OWASP Cheat Sheet Series - Cheatsheet foi criado para fornecer uma coleção concisa de informações de alto valor sobre tópicos específicos de segurança de aplicativos
- .NET Memory Performance Analysis - Este documento tem como objetivo ajudar as pessoas que desenvolvem aplicativos em .NET a pensar sobre a análise de desempenho de memória e a encontrar as abordagens corretas para realizar tal análise, se necessário. Neste contexto, o .NET inclui o .NET Framework e o .NET Core. Para obter as melhorias de memória mais recentes no coletor de lixo e no restante da estrutura, recomendo fortemente que você esteja no .NET Core, caso ainda não esteja, porque é onde o desenvolvimento ativo acontece
- naming-cheatsheet - Diretrizes abrangentes e independentes de linguagem sobre nomenclatura de variáveis
- 101 Design Patterns & Tips for Developers
- Go Concurrency Guide
