clean-code-dotnet 프로젝트를 좋아하거나 도움이된다면이 저장소에 대한 스타를 제공하십시오. 이는 .NET 커뮤니티를 강화하는 데 도움이 될뿐만 아니라 전 세계 .NET 개발자를위한 Clean Code에 대한 기술을 향상시킵니다. 매우 감사합니다 ?
내 블로그를 확인하거나 트위터에서 인사하십시오!
Robert C. Martin의 Book Clean Code 의 소프트웨어 엔지니어링 원칙은 .NET/.NET Core에 적용되었습니다. 이것은 스타일 가이드가 아닙니다. .NET/.NET Core에서 읽을 수 있고 재사용 가능하며 리팩토링 가능한 소프트웨어를 생성하는 가이드입니다.
본 문서의 모든 원칙이 엄격하게 준수되어야하는 것은 아니며, 보편적으로 합의 될 것입니다. 이것들은 지침이 아니며 더 이상 아무것도 아니지만 Clean Code 의 저자에 의해 수년간의 집단 경험에 걸쳐 체계화 된 것입니다.
Clean-Code-JavaScript 및 Clean Code-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 ) ;
}⬆ 위로 돌아갑니다
Magic Strings는 응용 프로그램의 동작에 영향을 미치는 응용 프로그램 코드 내에 직접 지정된 문자열 값입니다. 종종 이러한 문자열은 시스템 내에서 복제되며, 리팩토링 도구를 사용하여 자동으로 업데이트 할 수 없으므로 일부 문자열이 변경 될 때 일반적인 버그 소스가됩니다.
나쁜
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 ) ;
}좋은:
하위 패턴을 명명하여 Regex에 대한 의존도를 줄입니다.
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 ;
}⬆ 위로 돌아갑니다
플래그는이 방법에 둘 이상의 책임이 있음을 나타냅니다. 이 방법에 단일 책임 만있는 경우 가장 좋습니다. 부울 매개 변수가 메소드에 여러 책임이 추가되면 메소드를 2로 나눕니다.
나쁜:
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 ) ;
}⬆ 위로 돌아갑니다
Globals의 오염은 다른 라이브러리와 충돌 할 수 있고 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 인스턴스를 사용해야합니다.
⬆ 위로 돌아갑니다
싱글 톤은 반란 방지입니다. Brian Button에서 논문 :
문제의 근본에 대해 Misko Hevery의 매우 좋은 생각도 있습니다.
나쁜:
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 인스턴스를 사용해야합니다.
⬆ 위로 돌아갑니다
기능 매개 변수의 양을 제한하는 것은 기능 테스트를보다 쉽게 테스트 할 수 있기 때문에 매우 중요합니다. 3 개가 넘는 두 가지가 있으면 조합 폭발이 발생하여 각 별도의 인수로 다양한 사례를 테스트해야합니다.
제로 인수가 이상적인 경우입니다. 하나 또는 두 개의 인수는 괜찮고 세 가지는 피해야합니다. 그 이상은 통합되어야합니다. 일반적으로 두 가지 이상의 인수가 있다면 기능이 너무 많은 일을하려고합니다. 그렇지 않은 경우, 대부분의 경우 더 높은 수준의 객체는 논쟁으로 충분합니다.
나쁜:
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 ( ) ;
}⬆ 위로 돌아갑니다
4 명의 갱에 의해 디자인 패턴 으로 유명하게 언급 된 바와 같이, 당신은 당신이 할 수있는 상속을보다 선호해야합니다. 상속을 사용해야 할 많은 이유와 구성을 사용해야 할 좋은 이유가 많이 있습니다.
이 최대의 주요 요점은 당신의 마음이 본능적으로 상속을 받으면 구성이 문제를 더 잘 모델링 할 수 있는지 생각해보십시오. 어떤 경우에는 가능합니다.
"상속 재산을 언제 사용해야합니까?" 당면한 문제에 따라 다르지만, 상속이 구성보다 더 의미가있을 때의 괜찮은 목록입니다.
나쁜:
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는 Robert Martin이 지명 한 첫 5 개의 원칙에 대해 Michael Feathers가 소개 한 니모닉 약어입니다. 이는 객체 지향 프로그래밍 및 디자인의 5 가지 기본 원칙을 의미했습니다.
깨끗한 코드에 명시된 바와 같이, "클래스가 바뀌는 이유는 없어야한다". 비행에서 여행 가방을 하나만 가져갈 수있는 것처럼 많은 기능을 갖춘 클래스를 잼하는 유혹을 받고 있습니다. 이것의 문제는 당신의 수업이 개념적으로 응집력이 없으며 변화해야 할 많은 이유를 줄 것입니다. 수업을 변경하는 데 필요한 시간을 최소화하는 것이 중요합니다.
너무 많은 기능이 한 클래스에 있고 그것의 조각을 수정하면 코드베이스의 다른 종속 모듈에 어떤 영향을 미치는지 이해하기 어려울 수 있습니다.
나쁜:
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 ( ) )
{
// ...
}
}
}⬆ 위로 돌아갑니다
Bertrand Meyer가 언급 한 바와 같이, "소프트웨어 엔티티 (클래스, 모듈, 기능 등)는 확장을 위해 열려 있어야하지만 수정을 위해 닫혀야합니다." 그래도 그게 무슨 뜻입니까? 이 원칙은 기본적으로 기존 코드를 변경하지 않고도 사용자가 새로운 기능을 추가 할 수 있어야한다고 명시합니다.
나쁜:
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의 객체가 유형의 객체로 대체 될 수있다 (즉, 유형의 객체는 해당 프로그램의 바람직한 속성 (정확성, 작업 등)을 변경하지 않고 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 Core Framework에서 작업 한 경우 DI (Dependency Injection) 형태 로이 원칙을 구현하는 것을 보았습니다. 그것들은 동일한 개념이 아니지만 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 (Test Driven Development) 인 경우, 이는 훌륭하지만 주요 요점은 기능을 시작하기 전에 커버리지 목표에 도달하거나 기존 제품을 리팩토링하는 것입니다.
테스트가 레이저에 중점을두고 기타 (관련이없는) 물건을 테스트하지 않도록 보장합니다.
나쁜:
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 ) ;
}
}Sore https://www.codingblocks.net/podcast/how-to-write-amazing-unit-tests
⬆ 위로 돌아갑니다
비동기 프로그래밍 지침 요약
| 이름 | 설명 | 예외 |
|---|---|---|
| 비동기 무효를 피하십시오 | 비동기 무효 방법보다 비동기 작업 방법을 선호합니다 | 이벤트 처리기 |
| 모든 방향으로 비동기 | 차단과 비동기 코드를 혼합하지 마십시오 | 콘솔 기본 방법 (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은 IO 바운드 작업 (네트워킹 통신, 데이터베이스 통신, HTTP 요청 등)에 가장 적합하지만 계산 바운드 작업에 적용하는 것은 좋지 않습니다 (거대한 목록의 트래버스, Hugge 이미지 렌더링 등). 홀딩 스레드를 스레드 풀에 해제하고 사용 가능한 CPU/코어는 해당 작업을 처리하는 데 포함되지 않기 때문입니다. 따라서 컴퓨션 바운드 작업에 비동기/대기하는 것을 피해야합니다.
LongRunning 한 바운드 작업을 처리하려면 Task.Factory.CreateNew TaskCreationOptions 하는 것이 좋습니다. 새로운 배경 스레드를 시작하여 작업이 완료 될 때까지 스레드 풀로 다시 릴리스하지 않고도 무거운 계산 바운드 작업을 처리합니다.
도구를 알고 있습니다
비동기와 기다리기에 대해 배울 것이 많으며 약간 혼란스러워하는 것이 당연합니다. 다음은 일반적인 문제에 대한 솔루션에 대한 빠른 참조입니다.
일반적인 비동기 문제에 대한 솔루션
| 문제 | 해결책 |
|---|---|
| 코드를 실행하는 작업을 만듭니다 | Task.Run 또는 TaskFactory.StartNew ( Task 생성자 또는 Task.Start 가 아님) |
| 작업 또는 이벤트를위한 작업 래퍼를 만듭니다 | TaskFactory.FromAsync 또는 TaskCompletionSource<T> |
| 취소 지원 | CancellationTokenSource 및 CancellationToken |
| 보고서를보고합니다 | IProgress<T> 및 Progress<T> |
| 데이터 스트림을 처리합니다 | TPL 데이터 흐름 또는 반응성 확장 |
| 공유 리소스에 대한 액세스를 동기화합니다 | SemaphoreSlim |
| 자원을 비동기 초기화합니다 | AsyncLazy<T> |
| 비동기식 생산자/소비자 구조 | tpl dataflow 또는 AsyncCollection<T> |
작업 기반 비동기 패턴 (TAP) 문서를 읽으십시오. 매우 잘 작성되었으며 API 설계에 대한 지침과 Async/Await의 적절한 사용 (취소 및 진행보고 포함)이 포함됩니다.
오래된 차단 기술 대신 사용해야하는 많은 새로운 빈 친화적 기술이 있습니다. 새로운 비스듬한 코드 에이 오래된 예제가 있으면 잘못하고 있습니다 (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 Core)에서 기능 실행을 중지하고 스택 트레이스로 콘솔에서 귀하에게 알림으로써 알려줍니다.
잡힌 후 예외를 다시 제외 해야하는 경우이를 사용하여 '던지기'만 사용하면 스택 추적을 저장합니다. 그러나 아래의 나쁜 옵션에서는 스택 추적을 잃게됩니다.
나쁜:
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 Keyword를 사용하여 예외를 캐치 블록에서 다시 제외 할 수 있습니다. throw e; . 이 명령문은 스택 추적을 재설정합니다. 대신 throw; . 이것은 스택 추적을 유지하고 예외에 대한 더 깊은 통찰력을 제공합니다. 또 다른 옵션은 사용자 정의 예외를 사용하는 것입니다. 새로운 예외를 인스턴스화하고 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 라는 기능으로 대체 될 수 있으므로이 주석은 여전히 쓸모가 없습니다. 그러나 개발자가 SHA-1 또는 다른 해시 기능 대신 DJB2 해시 알고리즘을 선택한 이유를 코드별로 표현하기가 어려울 것입니다. 이 경우 의견이 허용됩니다.
좋은:
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 프로젝트에 기여한 모든 사람들에게 감사합니다.
우리의 일을 사랑하고 우리의 활동을 계속하도록 도와 주시겠습니까? [후원자가되기]
귀하의 사이트에 대한 링크를 통해 스폰서가되어 Github의 Readme에서 로고를 받으십시오. [후원자가되기]
법률에 따라 가능한 한, Thangchung 은이 사업에 대한 모든 저작권 및 관련 또는 이웃의 권리를 포기했습니다.