Если вам понравился проект clean-code-dotnet или если он вам помог, пожалуйста, дайте звезду для этого репозитория. Это не только поможет укрепить наше сообщество .NET, но и улучшить навыки чистого кода для разработчиков .NET во всем мире. Большое спасибо ?
Проверьте мой блог или скажите привет в Твиттере!
Принципы разработки программного обеспечения, от книги Роберта С. Мартина Чистого кода , адаптированы для .NET/.NET CORE. Это не руководство по стилю. Это руководство по производству читаемого, многоразового и рефактового программного обеспечения в .NET/.NET CORE.
Не каждый принцип в данном документе должен быть строго соблюдать, и еще меньше будет общепризнан. Это руководящие принципы и ничего более, но они кодифицированы на протяжении многих лет коллективного опыта авторами чистого кода .
Вдохновлен от списков чистого-кода-кода и чистого кода-PHP.
Плохой:
int d ;Хороший:
int daySinceModification ;⬆ Вернуться к вершине
Назовите переменную, чтобы отразить то, для чего она используется.
Плохой:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;Хороший:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ Вернуться к вершине
Венгерская нотация пересматривает тип, который уже присутствует в декларации. Это бессмысленно, так как современные IDE определят тип.
Плохой:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;Хороший:
int counter ;
string fullName ;
DateTime modifiedDate ;Венгерские записи также не должны использоваться в параметрах.
Плохой:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}Хороший:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ Вернуться к вершине
Капитализация рассказывает вам много о ваших переменных, функциях и т. Д. Эти правила субъективны, поэтому ваша команда может выбрать все, что они хотят. Дело в том, что независимо от того, что вы все выберете, просто будьте последовательны.
Плохой:
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 { }Хороший:
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 { }⬆ Вернуться к вершине
Потребуется время, чтобы исследовать значение переменных и функций, когда они не будут признаны.
Плохой:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}Хороший:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆ Вернуться к вершине
Используйте нотацию Camelcase для переменных и метода параметров.
Плохой:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}Хороший:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ Вернуться к вершине
Люди, которые читают ваш код, также являются программистами. Правильно назвать вещи поможет всем на одной странице. Мы не хотим тратить время, чтобы объяснить всем, для чего нужна переменная или функция.
Хороший
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 ( ) ;
}⬆ Вернуться к вершине
Слишком много операторов могут затруднить следование кода. Явное лучше, чем неявное .
Плохой:
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 ;
}
}Хороший:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}Плохой:
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" ) ;
}
}Хороший:
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 ) ;
}⬆ Вернуться к вершине
Не заставляйте читателя вашего кода перевести то, что означает переменная. Явное лучше, чем неявное .
Плохой:
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 ) ;
}Хороший:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆ Вернуться к вершине
Волшебные строки - это строковые значения, которые указаны непосредственно в коде приложения, которые влияют на поведение приложения. Часто такие строки в конечном итоге будут дублироваться в системе, и, поскольку они не могут автоматически обновляться с использованием инструментов рефакторинга, они становятся общим источником ошибок, когда изменения вносятся в некоторые строки, но не другие.
Плохой
if ( userRole == "Admin" )
{
// logic in here
}Хороший
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}Используя это, мы должны измениться только в централизуемом месте, а другие адаптируют его.
⬆ Вернуться к вершине
Если ваше имя класса/объекта говорит вам что -то, не повторяйте это в имени вашей переменной.
Плохой:
public class Car
{
public string CarMake { get ; set ; }
public string CarModel { get ; set ; }
public string CarColor { get ; set ; }
//...
}Хороший:
public class Car
{
public string Make { get ; set ; }
public string Model { get ; set ; }
public string Color { get ; set ; }
//...
}⬆ Вернуться к вершине
Плохой:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;Хороший:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆ Вернуться к вершине
Плохой:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;Хороший:
GetUser ( ) ;⬆ Вернуться к вершине
Мы прочитаем больше кода, чем когда -либо напишем. Важно, чтобы код, который мы пишем, читаемый и доступный для поиска. Не назвав переменные, которые в конечном итоге становятся значимыми для понимания нашей программы, мы повредим нашим читателям. Сделайте ваши имена доступными для поиска.
Плохой:
// 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 ( ) ) ;Хороший:
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 ( ) ) ;⬆ Вернуться к вершине
Плохой:
var data = new { Name = "John" , Age = 42 , PersonAccess = 4 } ;
// What the heck is 4 for?
if ( data . PersonAccess == 4 )
{
// do edit ...
}Хороший:
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 ...
}⬆ Вернуться к вершине
Плохой:
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 ) ;
}Хороший:
Уменьшить зависимость от корпорации путем именования подчиненных.
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 ) ;
}⬆ Вернуться к вершине
Не хорошо:
Это не хорошо, потому что breweryName может быть NULL .
Это мнение более понятно, чем предыдущая версия, но оно лучше управляет значением переменной.
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}Хороший:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆ Вернуться к вершине
Функция создает побочный эффект, если она делает что -то, кроме того, чтобы взять значение и вернуть другое значение или значения. Побочный эффект может быть написан в файл, изменение некоторой глобальной переменной или случайно подключение всех ваших денег незнакомцу.
Теперь вам нужно иметь побочные эффекты в программе. Как и в предыдущем примере, вам может потребоваться написать в файл. Что вы хотите сделать, так это централизовать, где вы это делаете. Нет нескольких функций и классов, которые записывают в определенный файл. Есть одна услуга, которая делает это. Один и только один.
Главный момент состоит в том, чтобы избежать общих ловушек, таких как состояние обмена между объектами без какой -либо структуры, используя изменяемые типы данных, которые могут быть записаны чем угодно, и не централизация, где возникают ваши побочные эффекты. Если вы сможете это сделать, вы будете счастливее, чем подавляющее большинство других программистов.
Плохой:
// 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Хороший:
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⬆ Вернуться к вершине
Плохой:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}Хороший:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ Вернуться к вершине
Это кажется невозможной задачей. Сначала услышав это, большинство людей говорят: «Как я должен что -то делать без заявления if ?» Ответ заключается в том, что вы можете использовать полиморфизм для достижения той же задачи во многих случаях. Второй вопрос обычно: «Ну, это здорово, но зачем мне это делать?» Ответ - предыдущая концепция чистого кода, которую мы выучили: функция должна делать только одну вещь. Если у вас есть классы и функции, которые if операторы, вы говорите своему пользователю, что ваша функция выполняет более одной вещи. Помните, просто сделайте одну вещь.
Плохой:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}Хороший:
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 ( ) ;
}
}⬆ Вернуться к вершине
Плохой:
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" ) ) ;
}
}Хороший:
public Path TravelToTexas ( Traveler vehicle )
{
vehicle . TravelTo ( new Location ( "texas" ) ) ;
}или
// 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" ) ) ;
}
}⬆ Вернуться к вершине
Плохой:
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 ;
}Хороший:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆ Вернуться к вершине
Флаг указывает, что метод несет более одной ответственности. Лучше всего, если метод имеет только одну ответственность. Разделите метод на два, если логический параметр добавляет несколько обязанностей в метод.
Плохой:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}Хороший:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆ Вернуться к вершине
Загрязняющие глобалы-плохая практика во многих языках, потому что вы могли бы столкнуться с другой библиотекой, и пользователь вашего API не будет более разумным, пока не получит исключение в производстве. Давайте подумаем о примере: что, если вы хотите иметь массив конфигурации. Вы можете написать глобальную функцию, например Config() , но она может столкнуться с другой библиотекой, которая пыталась сделать то же самое.
Плохой:
public Dictionary < string , string > Config ( )
{
return new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ;
}Хороший:
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 ;
}
} Загрузите конфигурацию и создайте экземпляр класса Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; И теперь вы должны использовать экземпляр Configuration в вашем приложении.
⬆ Вернуться к вершине
Синглтон-анти-паттерн. Перефразировано с Брайана Баттона:
Миско Хевери также есть очень хорошие мысли о корне проблемы.
Плохой:
class DBConnection
{
private static DBConnection _instance ;
private DBConnection ( )
{
// ...
}
public static GetInstance ( )
{
if ( _instance == null )
{
_instance = new DBConnection ( ) ;
}
return _instance ;
}
// ...
}
var singleton = DBConnection . GetInstance ( ) ;Хороший:
class DBConnection
{
public DBConnection ( IOptions < DbConnectionOption > options )
{
// ...
}
// ...
} Создайте экземпляр класса DBConnection и настройте его с помощью шаблона опции.
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; И теперь вы должны использовать экземпляр DBConnection в вашем приложении.
⬆ Вернуться к вершине
Ограничение количества параметров функции невероятно важно, потому что это облегчает тестирование вашей функции. Наличие более трех приводит к комбинаторному взрыву, когда вам нужно проверить тонны разных случаев с каждым отдельным аргументом.
Нулевые аргументы - идеальный случай. Один или два аргумента в порядке, а три следует избегать. Все больше, чем это должно быть консолидировано. Обычно, если у вас более двух аргументов, ваша функция пытается сделать слишком много. В тех случаях, когда это не так, в большинстве случаев объект более высокого уровня будет достаточно в качестве аргумента.
Плохой:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}Хороший:
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 )
{
// ...
}⬆ Вернуться к вершине
Это, безусловно, самое важное правило в разработке программного обеспечения. Когда функции выполняют более одной вещи, их сложнее составить, проверить и рассуждать. Когда вы можете изолировать функцию только одному действию, их можно легко рефактовать, и ваш код будет читать намного чище. Если вы ничего не уберете от этого руководства, кроме этого, вы будете впереди многих разработчиков.
Плохой:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}Хороший:
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" ) ;
}⬆ Вернуться к вершине
Плохой:
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 ( ) ;Хороший:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆ Вернуться к вершине
Еще не закончил
Когда у вас есть более одного уровня абстракции, ваша функция обычно делает слишком много. Расщепление функций приводит к повторному использованию и более простым тестированию.
Плохой:
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...
}
}Плохо тоже:
Мы выполнили часть функциональности, но функция ParseBetterJSAlternative() все еще очень сложная и не тестируемая.
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...
}
}Хороший:
Лучшее решение - вывести зависимости функции 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...
}
}
}⬆ Вернуться к вершине
Если функция вызывает другую, держите эти функции вертикально близко в исходном файле. В идеале держите вызывающего абонента прямо над Callee. Мы склонны читать код от верхнего до дна, как газета. Из -за этого заставьте ваш код прочитать таким образом.
Плохой:
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 ( ) ;Хороший:
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 ( ) ;⬆ Вернуться к вершине
Плохой:
if ( article . state == "published" )
{
// ...
}Хороший:
if ( article . IsPublished ( ) )
{
// ...
}⬆ Вернуться к вершине
Мертвый код такой же плохой, как дубликат кода. Нет причин сохранить его в вашей кодовой базе. Если его не называют, избавьтесь от этого! Это все равно будет безопасно в истории вашей версии, если вам все еще нужно.
Плохой:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;Хороший:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆ Вернуться к вершине
В C# / vb.net вы можете установить public , protected и private ключевые слова для методов. Используя его, вы можете управлять модификацией свойств на объекте.
set .Кроме того, это является частью открытого/закрытого принципа, от объектно-ориентированных принципов дизайна.
Плохой:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;Хороший:
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 ;⬆ Вернуться к вершине
Плохой:
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Хороший:
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⬆ Вернуться к вершине
Этот шаблон очень полезен и обычно используется во многих библиотеках. Это позволяет вашему коду быть выразительным и менее многословным. По этой причине используйте метод цепочки и посмотрите, насколько чистым будет ваш код.
Хороший:
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 ( ) ;
}⬆ Вернуться к вершине
Как известно, известно в шаблонах проектирования бандой из четырех, вы должны предпочесть композицию над наследством, где вы можете. Есть много веских причин для использования наследования и множество веских причин для использования композиции.
Основной момент для этого максимума заключается в том, что если ваш ум инстинктивно идет на наследство, постарайтесь подумать, может ли композиция лучше моделировать вашу проблему. В некоторых случаях это может.
Вам может быть интересно: «Когда мне следует использовать наследство?» Это зависит от вашей проблемы под рукой, но это приличный список, когда наследование имеет больше смысла, чем композиция:
Плохой:
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 )
{
// ...
}
// ...
}Хороший:
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 ) ;
}
// ...
}⬆ Вернуться к вершине
Solid -это мнемоническая аббревиатура, представленная Майклом Файперс для первых пяти принципов, названных Робертом Мартином, что означало пять основных принципов объектно-ориентированного программирования и дизайна.
Как указано в чистом коде, «никогда не должно быть более одной причины для изменения класса». Соблазнительно погрузиться в класс с большим количеством функциональности, например, когда вы можете взять только один чемодан в свой рейс. Проблема в том, что ваш класс не будет концептуально сплоченным, и это даст ему много причин для изменения. Важно минимизировать количество раз, когда вам нужно изменить класс.
Это важно, потому что, если слишком много функциональности находится в одном классе, и вы измените его часть, может быть трудно понять, как это повлияет на другие зависимые модули в вашей кодовой базе.
Плохой:
class UserSettings
{
private User User ;
public UserSettings ( User user )
{
User = user ;
}
public void ChangeSettings ( Settings settings )
{
if ( verifyCredentials ( ) )
{
// ...
}
}
private bool VerifyCredentials ( )
{
// ...
}
}Хороший:
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 ( ) )
{
// ...
}
}
}⬆ Вернуться к вершине
Как указано Бертран Мейер, «Программные предприятия (классы, модули, функции и т. Д.) должны быть открыты для расширения, но закрыты для модификации». Что это значит, хотя? Этот принцип в основном утверждает, что вы должны позволить пользователям добавлять новые функциональные возможности без изменения существующего кода.
Плохой:
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
}
}Хороший:
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 ) ;
}
}⬆ Вернуться к вершине
Это страшный термин для очень простой концепции. Он формально определяется как «если s является подтипом t, то объекты типа T могут быть заменены объектами типа S (то есть объекты типа S могут заменить объекты типа T) без изменения любого из желательных свойств этой программы (правильная задача, выполненная задача и т. Д.)». Это еще более страшное определение.
Лучшее объяснение для этого - если у вас есть родительский класс и урок ребенка, то базовый класс и детский класс можно использовать взаимозаменяемо, не получая неправильных результатов. Это все еще может быть запутанным, так что давайте посмотрим на классический пример квадратного реэтальга. Математически, квадрат-это прямоугольник, но если вы моделируете его, используя отношения «is-a» через наследство, вы быстро попадаете в беду.
Плохой:
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 ) ;Хороший:
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 ) ;⬆ Вернуться к вершине
ISP заявляет, что «клиенты не должны быть вынуждены зависеть от интерфейсов, которые они не используют».
Хороший пример, чтобы посмотреть на это, демонстрирует этот принцип для классов, которые требуют больших объектов настроек. Не требует, чтобы клиенты настраивали огромные объемы опций, является полезным, потому что большую часть времени им не понадобятся все настройки. Сделать их необязательными помогает предотвратить «жирный интерфейс».
Плохой:
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
}
}Хороший:
Не каждый работник является работником, но каждый сотрудник - работник.
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
}
}⬆ Вернуться к вершине
Этот принцип содержит две важные вещи:
Сначала это может быть трудно понять, но если вы работали с основной структурой .NET/.NET, вы видели реализацию этого принципа в форме инъекции зависимости (DI). Несмотря на то, что они не являются идентичными понятиями, DIP не дает модулям высокого уровня знать детали его низкоуровневых модулей и настраивать их. Это может сделать это через Di. Огромное преимущество этого заключается в том, что он уменьшает связь между модулями. Соединение - очень плохая образец разработки, потому что она затрудняет рефактор.
Плохой:
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 ( ) ;
}
}Хороший:
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 ( ) ;
}
}
}⬆ Вернуться к вершине
Попробуйте соблюдать сухой принцип.
Сделайте все возможное, чтобы избежать дубликата кода. Дубликат код плох, потому что это означает, что есть более одного места, чтобы что -то изменить, если вам нужно изменить логику.
Представьте, что если вы запускаете ресторан и отслеживаете свой инвентарь: все ваши помидоры, лук, чеснок, специи и т. Д. Если у вас есть несколько списков, которые вы сохраняете, то все должны быть обновлены, когда вы подаете блюдо с помидорами в них. Если у вас есть только один список, есть только одно место для обновления!
Часто у вас есть дублированный код, потому что у вас есть две или более разных вещей, которые имеют много общего, но их различия заставляют вас выполнять две или более отдельных функций, которые выполняют много одинаковых вещей. Удаление дублированного кода означает создание абстракции, которая может обрабатывать этот набор разных вещей только с одной функцией/модулем/классом.
Правильное получение абстракции имеет решающее значение, поэтому вы должны следовать солидным принципам, изложенным в разделе классов. Плохие абстракции могут быть хуже, чем дубликат кода, так что будьте осторожны! Сказав это, если вы можете сделать хорошую абстракцию, сделайте это! Не повторяйте себя, иначе вы обновите несколько мест в любое время, когда хотите изменить одну вещь.
Плохой:
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 ) ;
}
}Хороший:
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 ) ;
}
}Очень хороший:
Лучше использовать компактную версию кода.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ Вернуться к вершине
Тестирование важнее доставки. Если у вас нет тестов или неадекватной суммы, то каждый раз, когда вы отправляете код, вы не будете уверены, что ничего не сломаете. Решение о том, что представляет собой достаточную сумму, зависит от вашей команды, но иметь 100% охват (все заявления и филиалы) - это то, как вы достигаете очень высокой уверенности и душевного спокойствия. Это означает, что в дополнение к отличной структуре тестирования вам также необходимо использовать хороший инструмент покрытия.
Там нет оправдания, чтобы не писать тесты. Есть много хороших тестовых фрейм .NET, так что найдите ту, которую предпочитает ваша команда. Когда вы найдете тот, который работает для вашей команды, затем стремитесь всегда писать тесты для каждой новой функции/модуля, который вы представляете. Если ваш предпочтительный метод - это разработка тестового управления (TDD), это здорово, но главное - просто убедиться, что вы достигаете своих целей покрытия перед запуском какой -либо функции, или рефакторировать существующую.
Обеспечивает, чтобы ваши тесты были сосредоточены на лазере и не тестируют разной (не связанные) вещи, заставляет AAA Patern, используемые ваши коды более чистыми и читаемыми.
Плохой:
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 ) ;
}
}Хороший:
public class MakeDotNetGreatAgainTests
{
[ Fact ]
public void Handle30DayMonths ( )
{
// Arrange
var date = new MyDateTime ( "1/1/2015" ) ;
// Act
date . AddDays ( 30 ) ;
// Assert
Assert . Equal ( "1/31/2015" , date ) ;
}
[ Fact ]
public void HandleLeapYear ( )
{
// Arrange
var date = new MyDateTime ( "2/1/2016" ) ;
// Act
date . AddDays ( 28 ) ;
// Assert
Assert . Equal ( "02/29/2016" , date ) ;
}
[ Fact ]
public void HandleNonLeapYear ( )
{
// Arrange
var date = new MyDateTime ( "2/1/2015" ) ;
// Act
date . AddDays ( 28 ) ;
// Assert
Assert . Equal ( "03/01/2015" , date ) ;
}
}Soure https://www.codingblocks.net/podcast/how-to-write-amazing-unit-tests
⬆ Вернуться к вершине
Резюме асинхронных руководящих принципов программирования
| Имя | Описание | Исключения |
|---|---|---|
| Избегайте Async void | Предпочитаю асинхронные методы задачи над асинхронными пустотыми методами | Обработчики событий |
| Асинхрон | Не смешивайте блокировку и асинхронный код | Основной метод консоли (C# <= 7,0) |
| Настройка контекста | Используйте ConfigureAwait(false) когда вы можете | Методы, которые требуют контекста |
Асинхронный способ ведения дела
| Чтобы сделать это ... | Вместо этого ... | Используйте это |
|---|---|---|
| Получить результат фоновой задачи | Task.Wait or Task.Result | await |
| Дождись выполнения любой задачи | Task.WaitAny | await Task.WhenAny |
| Получить результаты нескольких задач | Task.WaitAll | await Task.WhenAll |
| Подождите | Thread.Sleep | await Task.Delay |
Лучшая практика
Async/await является лучшим для задач, связанных с вводом ввода, (сетевая связь, общение с базой данных, HTTP -запрос и т. Д.), Но нехорошо применять задачи вычислительных связанных (пройдений в огромном списке, отображать изображение Hugge и т. Д.). Поскольку он выпустит потоковую систему в пул потоков, а CPU/Cores, доступные не будут включать в себя обработку этих задач. Поэтому мы должны избегать использования Async/ждать для составных связанных задач.
Для борьбы с вычислительными задачами LongRunning использовать Task.Factory.CreateNew с TaskCreationOptions Он запустит новый фоновый поток для обработки тяжелой вычислительной связанной задачи, не выпустите ее обратно в пул потоков до завершения задачи.
Знай свои инструменты
Есть много, чтобы узнать об асинхронности и ждать, и естественно, чтобы немного дезориентироваться. Вот быстрое упоминание решений общих проблем.
Решения общих асинхронных задач
| Проблема | Решение |
|---|---|
| Создайте задачу для выполнения кода | Task.Run или TaskFactory.StartNew (не конструктор Task или Task.Start . |
| Создайте обертку задачи для операции или события | TaskFactory.FromAsync или TaskCompletionSource<T> |
| Отмена поддержки | CancellationTokenSource CancellationToken |
| Сообщить о прогрессе | IProgress<T> и Progress<T> |
| Обрабатывать потоки данных | TPL DataFlow или реактивные расширения |
| Синхронизировать доступ к общему ресурсу | SemaphoreSlim |
| Асинхронно инициализируйте ресурс | AsyncLazy<T> |
| Асинк-готовые производители/потребительские структуры | TPL DataFlow или AsyncCollection<T> |
Прочитайте документ с асинхронным шаблоном (TAP) на основе задач. Он чрезвычайно хорошо написан и включает в себя руководство по проектированию API и правильное использование Async/watiate (включая отчеты и отчеты о прогрессе).
Существует множество новых методов, удобных для ожидания, которые следует использовать вместо старых методов блокировки. Если у вас есть какие -либо из этих старых примеров в вашем новом асинхровом коде, вы делаете это неправильно (TM):
| Старый | Новый | Описание |
|---|---|---|
task.Wait | await task | Ждите/ожидайте задачи, чтобы выполнить |
task.Result | await task | Получите результат выполненной задачи |
Task.WaitAny | await Task.WhenAny | Ждите/ожидайте одной из коллекций задач, чтобы выполнить |
Task.WaitAll | await Task.WhenAll | Подождите/ожидайте каждой коллекции задач, чтобы выполнить |
Thread.Sleep | await Task.Delay | Подождите/ждите некоторое время |
Task конструктор | Task.Run или TaskFactory.StartNew | Создать задачу на основе кода |
Источник https://gist.github.com/jonlabelle/841146854B23B305B50FA5542F84B20C
⬆ Вернуться к вершине
Ошибки брошены хорошей вещью! Они означают, что время выполнения успешно определило, когда что -то в вашей программе пошло не так, и оно дает вам знать, останавливая выполнение функции в текущем стеке, убивая процесс (в ядре .NET/.NET) и уведомляя вас в консоли с помощью трассировки стека.
Если вам нужно переоценить исключение после его поймы, используйте просто «брось», используя это, вы сохраните трассу стека. Но в приведенном ниже варианте вы потеряете трассировку стека.
Плохой:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ex ;
}Хороший:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ;
}⬆ Вернуться к вершине
Ничего не делая с захваченной ошибкой, не дает вам возможности когда -либо исправлять или отреагировать на указанную ошибку. Бросить ошибку не намного лучше, так как часто она может терять в море вещей, напечатанных в консоли. Если вы оберните какой -либо код в try/catch это означает, что вы думаете, что там может возникнуть ошибка, и поэтому у вас должен быть план или создать путь кода, поскольку, когда это происходит.
Плохой:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}Хороший:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆ Вернуться к вершине
Если вам нужно принять меры в соответствии с типом исключения, вы лучше используете несколько блока улова для обработки исключений.
Плохой:
try
{
// Do something..
}
catch ( Exception ex )
{
if ( ex is TaskCanceledException )
{
// Take action for TaskCanceledException
}
else if ( ex is TaskSchedulerException )
{
// Take action for TaskSchedulerException
}
}Хороший:
try
{
// Do something..
}
catch ( TaskCanceledException ex )
{
// Take action for TaskCanceledException
}
catch ( TaskSchedulerException ex )
{
// Take action for TaskSchedulerException
}⬆ Вернуться к вершине
C# позволяет повторному исключению в блоке вылова, используя ключевое слово throw . Это плохая практика, чтобы бросить пойманное исключение, используя throw e; Анкет Это утверждение сбрасывает трассировку стека. Вместо этого используйте throw; Анкет Это сохранит след стека и даст более глубокое представление о исключении. Другой вариант - использовать пользовательское исключение. Просто создайте создание нового исключения и установите свое внутреннее исключение свойство на завоеванное исключение с помощью Thress new CustomException("some info", e); Анкет Добавление информации к исключению является хорошей практикой, так как она поможет в отладке. Однако, если цель состоит в том, чтобы записать исключение, то используйте throw; передать доллар звонивцу.
Плохой:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
logger . LogInfo ( ex ) ;
throw ex ;
}Хороший:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw ;
}Хороший:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw new CustomException ( error ) ;
}⬆ Вернуться к вершине
Плохой:
В проекте есть много стилей форматирования кода. Например, стиль отступления - это space и tab смешанные в проекте.
Хороший:
Определите и поддерживайте согласованный стиль кода в вашей кодовой базе с использованием файла .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⬆ Вернуться к вершине
Они обычно просто добавляют шум. Пусть функции и имена переменных вместе с правильным вдавлением и форматированием придают визуальную структуру вашему коду.
Плохой:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;Плохой:
#region Scope Model Instantiation
var model = {
menu : 'foo' ,
nav : 'bar'
} ;
#endregion
#region Action setup
void Actions ( ) {
// ...
} ;
#endregionХороший:
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
void Actions ( )
{
// ...
} ;⬆ Вернуться к вершине
Управление версиями существует по причине. Оставьте старый код в своей истории.
Плохой:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Хороший:
doStuff ( ) ;⬆ Вернуться к вершине
Помните, используйте контроль версий! Там нет необходимости в мертвом коде, комментированном коде и особенно комментариях журнала. Используйте git log , чтобы получить историю!
Плохой:
/**
* 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 ;
}Хороший:
public int Combine ( int a , int b )
{
return a + b ;
}⬆ Вернуться к вершине
Комментарии - это извинение, а не требование. Хороший код в основном документирует себя.
Плохой:
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 ;
}
}Лучше, но все же плохо:
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 ;
}
} Если комментарий объясняет, что делает код, это, вероятно, бесполезный комментарий и может быть реализован с хорошо именованной переменной или функцией. Комментарий в предыдущем коде может быть заменен функцией с именем ConvertTo32bitInt , поэтому этот комментарий все еще бесполезен. Однако было бы трудно выразить кодом, почему разработчик выбрал алгоритм хэш-аш DJB2 вместо SHA-1 или другой функции хэш. В этом случае комментарий приемлем.
Хороший:
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-dotnet
Любите нашу работу и помогите нам продолжить нашу деятельность? [Станьте покровителем]
Станьте спонсором и получите свой логотип в нашем Readme на GitHub по ссылке на ваш сайт. [Станьте спонсором]
В той мере, что в соответствии с законом, Танчунг отказался от всех авторских прав и связанных или соседних прав на эту работу.