Jika Anda menyukai proyek clean-code-dotnet atau jika itu membantu Anda, harap berikan bintang untuk repositori ini. Itu tidak hanya akan membantu memperkuat komunitas .NET kami tetapi juga meningkatkan keterampilan tentang kode bersih untuk pengembang .NET di seluruh dunia. Terima kasih banyak ?
Lihatlah blog saya atau menyapa di Twitter!
Prinsip -prinsip Rekayasa Perangkat Lunak, dari buku Clean Book Clean Code Robert C. Martin, diadaptasi untuk .NET/.NET Core. Ini bukan panduan gaya. Ini adalah panduan untuk memproduksi perangkat lunak yang dapat dibaca, dapat digunakan kembali, dan dapat dikenakan kembali di .NET/.NET Core.
Tidak setiap prinsip di sini harus diikuti secara ketat, dan bahkan lebih sedikit yang akan disepakati secara universal. Ini adalah pedoman dan tidak lebih, tetapi mereka dikodifikasi selama bertahun -tahun pengalaman kolektif oleh penulis kode bersih .
Terinspirasi dari daftar Clean-Code-JavaScript dan Clean-Code-PHP.
Buruk:
int d ;Bagus:
int daySinceModification ;⬆ Kembali ke atas
Sebutkan variabel untuk mencerminkan apa yang digunakan.
Buruk:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;Bagus:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ Kembali ke atas
Notasi Hongaria menyatakan kembali jenis yang sudah ada dalam deklarasi. Ini tidak ada gunanya karena IDE modern akan mengidentifikasi jenisnya.
Buruk:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;Bagus:
int counter ;
string fullName ;
DateTime modifiedDate ;Notasi Hongaria juga tidak boleh digunakan dalam paramaters.
Buruk:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}Bagus:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ Kembali ke atas
Kapitalisasi memberi tahu Anda banyak tentang variabel, fungsi, dll. Aturan ini subyektif, sehingga tim Anda dapat memilih apa pun yang mereka inginkan. Intinya, tidak peduli apa yang Anda semua pilih, bersikaplah konsisten.
Buruk:
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 { }Bagus:
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 { }⬆ Kembali ke atas
Butuh waktu untuk menyelidiki arti variabel dan fungsi ketika mereka tidak dapat diucapkan.
Buruk:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}Bagus:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆ Kembali ke atas
Gunakan notasi CamelCase untuk Paramaters Variabel dan Metode.
Buruk:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}Bagus:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ Kembali ke atas
Orang yang membaca kode Anda juga programmer. Penamaan hal yang benar akan membantu semua orang berada di halaman yang sama. Kami tidak ingin meluangkan waktu untuk menjelaskan kepada semua orang apa variabel atau fungsi.
Bagus
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 ( ) ;
}⬆ Kembali ke atas
Terlalu banyak jika pernyataan lain dapat membuat kode sulit diikuti. Eksplisit lebih baik daripada implisit .
Buruk:
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 ;
}
}Bagus:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}Buruk:
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" ) ;
}
}Bagus:
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 ) ;
}⬆ Kembali ke atas
Jangan paksa pembaca kode Anda untuk menerjemahkan apa arti variabelnya. Eksplisit lebih baik daripada implisit .
Buruk:
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 ) ;
}Bagus:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆ Kembali ke atas
Magic Strings adalah nilai string yang ditentukan langsung dalam kode aplikasi yang berdampak pada perilaku aplikasi. Seringkali, string seperti itu akan berakhir digandakan dalam sistem, dan karena mereka tidak dapat secara otomatis diperbarui menggunakan alat refactoring, mereka menjadi sumber bug yang umum ketika perubahan dilakukan pada beberapa string tetapi tidak yang lain.
Buruk
if ( userRole == "Admin" )
{
// logic in here
}Bagus
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}Menggunakan ini kita hanya perlu berubah di tempat sentralisasi dan lainnya akan mengadaptasinya.
⬆ Kembali ke atas
Jika nama kelas/objek Anda memberi tahu Anda sesuatu, jangan ulangi itu di nama variabel Anda.
Buruk:
public class Car
{
public string CarMake { get ; set ; }
public string CarModel { get ; set ; }
public string CarColor { get ; set ; }
//...
}Bagus:
public class Car
{
public string Make { get ; set ; }
public string Model { get ; set ; }
public string Color { get ; set ; }
//...
}⬆ Kembali ke atas
Buruk:
var ymdstr = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;Bagus:
var currentDate = DateTime . UtcNow . ToString ( "MMMM dd, yyyy" ) ;⬆ Kembali ke atas
Buruk:
GetUserInfo ( ) ;
GetUserData ( ) ;
GetUserRecord ( ) ;
GetUserProfile ( ) ;Bagus:
GetUser ( ) ;⬆ Kembali ke atas
Kami akan membaca lebih banyak kode daripada yang pernah kami tulis. Penting bahwa kode yang kami tulis dapat dibaca dan dicari. Dengan tidak memberi nama variabel yang akhirnya bermakna untuk memahami program kami, kami melukai pembaca kami. Membuat nama Anda bisa dicari.
Buruk:
// 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 ( ) ) ;Bagus:
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 ( ) ) ;⬆ Kembali ke atas
Buruk:
var data = new { Name = "John" , Age = 42 , PersonAccess = 4 } ;
// What the heck is 4 for?
if ( data . PersonAccess == 4 )
{
// do edit ...
}Bagus:
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 ...
}⬆ Kembali ke atas
Buruk:
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 ) ;
}Bagus:
Kurangi ketergantungan pada regex dengan menyebutkan subpatterns.
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 ) ;
}⬆ Kembali ke atas
Tidak Baik:
Ini tidak bagus karena breweryName bisa NULL .
Pendapat ini lebih dapat dimengerti daripada versi sebelumnya, tetapi lebih baik mengontrol nilai variabel.
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}Bagus:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆ Kembali ke atas
Fungsi menghasilkan efek samping jika melakukan apa pun selain mengambil nilai masuk dan mengembalikan nilai atau nilai lain. Efek samping dapat menulis ke file, memodifikasi beberapa variabel global, atau secara tidak sengaja kabel semua uang Anda ke orang asing.
Sekarang, Anda perlu memiliki efek samping dalam suatu program pada suatu kesempatan. Seperti contoh sebelumnya, Anda mungkin perlu menulis ke file. Yang ingin Anda lakukan adalah memusatkan di mana Anda melakukan ini. Tidak memiliki beberapa fungsi dan kelas yang menulis ke file tertentu. Memiliki satu layanan yang melakukannya. Satu dan satu -satunya.
Poin utamanya adalah untuk menghindari jebakan umum seperti berbagi keadaan antara objek tanpa struktur apa pun, menggunakan tipe data yang dapat berubah yang dapat ditulis oleh apa pun, dan tidak memusatkan di mana efek samping Anda terjadi. Jika Anda dapat melakukan ini, Anda akan lebih bahagia daripada sebagian besar programmer lain.
Buruk:
// 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 McDermottBagus:
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⬆ Kembali ke atas
Buruk:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}Bagus:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ Kembali ke atas
Ini sepertinya tugas yang mustahil. Setelah pertama kali mendengar ini, kebanyakan orang berkata, "Bagaimana saya bisa melakukan sesuatu tanpa pernyataan if ?" Jawabannya adalah Anda dapat menggunakan polimorfisme untuk mencapai tugas yang sama dalam banyak kasus. Pertanyaan kedua biasanya, "Yah itu bagus tapi mengapa saya ingin melakukan itu?" Jawabannya adalah konsep kode bersih sebelumnya yang kami pelajari: suatu fungsi hanya boleh melakukan satu hal. Ketika Anda memiliki kelas dan fungsi yang memiliki pernyataan if , Anda memberi tahu pengguna Anda bahwa fungsi Anda melakukan lebih dari satu hal. Ingat, lakukan saja satu hal.
Buruk:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}Bagus:
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 ( ) ;
}
}⬆ Kembali ke atas
Buruk:
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" ) ) ;
}
}Bagus:
public Path TravelToTexas ( Traveler vehicle )
{
vehicle . TravelTo ( new Location ( "texas" ) ) ;
}atau
// 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" ) ) ;
}
}⬆ Kembali ke atas
Buruk:
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 ;
}Bagus:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆ Kembali ke atas
Bendera menunjukkan bahwa metode ini memiliki lebih dari satu tanggung jawab. Yang terbaik jika metode ini hanya memiliki satu tanggung jawab. Pisahkan metode menjadi dua jika parameter boolean menambahkan beberapa tanggung jawab pada metode ini.
Buruk:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}Bagus:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆ Kembali ke atas
Global yang mencemari adalah praktik yang buruk dalam banyak bahasa karena Anda dapat berbenturan dengan perpustakaan lain dan pengguna API Anda tidak akan lebih bijaksana sampai mereka mendapatkan pengecualian dalam produksi. Mari kita pikirkan contoh: bagaimana jika Anda ingin memiliki array konfigurasi. Anda dapat menulis fungsi global seperti Config() , tetapi bisa berbenturan dengan perpustakaan lain yang mencoba melakukan hal yang sama.
Buruk:
public Dictionary < string , string > Config ( )
{
return new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ;
}Bagus:
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 ;
}
} Memuat konfigurasi dan membuat instance kelas Configuration
var configuration = new Configuration ( new Dictionary < string , string > ( ) {
[ "foo" ] = "bar"
} ) ; Dan sekarang Anda harus menggunakan contoh Configuration di aplikasi Anda.
⬆ Kembali ke atas
Singleton adalah anti-pola. Parafrase dari Brian Button:
Ada juga pemikiran yang sangat baik oleh Misko Hevery tentang akar masalah.
Buruk:
class DBConnection
{
private static DBConnection _instance ;
private DBConnection ( )
{
// ...
}
public static GetInstance ( )
{
if ( _instance == null )
{
_instance = new DBConnection ( ) ;
}
return _instance ;
}
// ...
}
var singleton = DBConnection . GetInstance ( ) ;Bagus:
class DBConnection
{
public DBConnection ( IOptions < DbConnectionOption > options )
{
// ...
}
// ...
} Buat contoh kelas DBConnection dan konfigurasikan dengan pola opsi.
var options = < resolve from IOC > ;
var connection = new DBConnection ( options ) ; Dan sekarang Anda harus menggunakan instance DBConnection di aplikasi Anda.
⬆ Kembali ke atas
Membatasi jumlah parameter fungsi sangat penting karena membuat pengujian fungsi Anda lebih mudah. Memiliki lebih dari tiga mengarah ke ledakan kombinatorial di mana Anda harus menguji banyak kasus yang berbeda dengan setiap argumen terpisah.
Nol argumen adalah kasus yang ideal. Satu atau dua argumen OK, dan tiga harus dihindari. Sesuatu yang lebih dari itu harus dikonsolidasikan. Biasanya, jika Anda memiliki lebih dari dua argumen maka fungsi Anda mencoba melakukan terlalu banyak. Dalam kasus di mana tidak, sebagian besar waktu objek tingkat yang lebih tinggi akan cukup sebagai argumen.
Buruk:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}Bagus:
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 )
{
// ...
}⬆ Kembali ke atas
Sejauh ini, ini adalah aturan terpenting dalam rekayasa perangkat lunak. Ketika fungsi melakukan lebih dari satu hal, mereka lebih sulit untuk menyusun, menguji, dan bernalar. Ketika Anda dapat mengisolasi fungsi hanya untuk satu tindakan, mereka dapat dengan mudah direktor dan kode Anda akan membaca lebih bersih. Jika Anda tidak mengambil apa pun dari panduan ini selain ini, Anda akan berada di depan banyak pengembang.
Buruk:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}Bagus:
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" ) ;
}⬆ Kembali ke atas
Buruk:
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 ( ) ;Bagus:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆ Kembali ke atas
Belum selesai
Ketika Anda memiliki lebih dari satu tingkat abstraksi fungsi Anda biasanya melakukan terlalu banyak. Memisahkan fungsi mengarah pada reusability dan pengujian yang lebih mudah.
Buruk:
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...
}
}Buruk juga:
Kami telah melakukan beberapa fungsi, tetapi fungsi ParseBetterJSAlternative() masih sangat kompleks dan tidak dapat diuji.
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...
}
}Bagus:
Solusi terbaik adalah memindahkan ketergantungan fungsi 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...
}
}
}⬆ Kembali ke atas
Jika suatu fungsi memanggil yang lain, jaga agar fungsi -fungsi tersebut tutup secara vertikal di file sumber. Idealnya, simpan penelepon tepat di atas callee. Kami cenderung membaca kode dari atas ke bawah, seperti koran. Karena itu, buat kode Anda dibaca seperti itu.
Buruk:
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 ( ) ;Bagus:
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 ( ) ;⬆ Kembali ke atas
Buruk:
if ( article . state == "published" )
{
// ...
}Bagus:
if ( article . IsPublished ( ) )
{
// ...
}⬆ Kembali ke atas
Kode mati sama buruknya dengan kode duplikat. Tidak ada alasan untuk menyimpannya di basis kode Anda. Jika tidak dipanggil, singkirkan! Ini akan tetap aman dalam riwayat versi Anda jika Anda masih membutuhkannya.
Buruk:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;Bagus:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆ Kembali ke atas
Dalam C# / VB.NET Anda dapat mengatur kata kunci public , protected , dan private untuk metode. Menggunakannya, Anda dapat mengontrol modifikasi properti pada suatu objek.
set .Selain itu, ini adalah bagian dari prinsip terbuka/tertutup, dari prinsip desain yang berorientasi objek.
Buruk:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;Bagus:
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 ;⬆ Kembali ke atas
Buruk:
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 DoeBagus:
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⬆ Kembali ke atas
Pola ini sangat berguna dan umum digunakan di banyak perpustakaan. Ini memungkinkan kode Anda menjadi ekspresif, dan lebih sedikit bertele -tele. Untuk alasan itu, gunakan rantai metode dan lihat seberapa bersih kode Anda.
Bagus:
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 ( ) ;
}⬆ Kembali ke atas
Sebagaimana dinyatakan terkenal dalam pola desain oleh geng empat, Anda harus lebih suka komposisi daripada warisan di mana Anda bisa. Ada banyak alasan bagus untuk menggunakan warisan dan banyak alasan bagus untuk menggunakan komposisi.
Poin utama untuk pepatah ini adalah bahwa jika pikiran Anda secara naluriah berlaku untuk warisan, cobalah untuk berpikir jika komposisi dapat memodelkan masalah Anda dengan lebih baik. Dalam beberapa kasus itu bisa.
Anda mungkin bertanya -tanya kemudian, "Kapan saya harus menggunakan warisan?" Itu tergantung pada masalah Anda, tetapi ini adalah daftar yang layak saat waris lebih masuk akal daripada komposisi:
Buruk:
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 )
{
// ...
}
// ...
}Bagus:
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 ) ;
}
// ...
}⬆ Kembali ke atas
Solid adalah akronim mnemonik yang diperkenalkan oleh Michael Feathers untuk lima prinsip pertama yang disebutkan oleh Robert Martin, yang berarti lima prinsip dasar pemrograman dan desain yang berorientasi objek.
Seperti yang dinyatakan dalam kode bersih, "seharusnya tidak pernah ada lebih dari satu alasan bagi kelas untuk berubah". Sangat menggoda untuk meng-pack kelas dengan banyak fungsi, seperti ketika Anda hanya bisa mengambil satu koper di penerbangan Anda. Masalah dengan ini adalah bahwa kelas Anda tidak akan kohesif secara konseptual dan itu akan memberikan banyak alasan untuk berubah. Meminimalkan berapa kali Anda perlu mengubah kelas adalah penting.
Ini penting karena jika terlalu banyak fungsionalitas dalam satu kelas dan Anda memodifikasi bagiannya, mungkin sulit untuk memahami bagaimana hal itu akan memengaruhi modul dependen lainnya dalam basis kode Anda.
Buruk:
class UserSettings
{
private User User ;
public UserSettings ( User user )
{
User = user ;
}
public void ChangeSettings ( Settings settings )
{
if ( verifyCredentials ( ) )
{
// ...
}
}
private bool VerifyCredentials ( )
{
// ...
}
}Bagus:
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 ( ) )
{
// ...
}
}
}⬆ Kembali ke atas
Seperti yang dinyatakan oleh Bertrand Meyer, "Entitas Perangkat Lunak (Kelas, Modul, Fungsi, dll.) Harus terbuka untuk ekstensi, tetapi ditutup untuk modifikasi." Apa artinya itu? Prinsip ini pada dasarnya menyatakan bahwa Anda harus mengizinkan pengguna untuk menambahkan fungsionalitas baru tanpa mengubah kode yang ada.
Buruk:
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
}
}Bagus:
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 ) ;
}
}⬆ Kembali ke atas
Ini adalah istilah yang menakutkan untuk konsep yang sangat sederhana. Ini secara resmi didefinisikan sebagai "jika S adalah subtipe T, maka objek tipe T dapat diganti dengan objek tipe S (yaitu, objek tipe S dapat menggantikan objek tipe T) tanpa mengubah salah satu sifat yang diinginkan dari program tersebut (kebenaran, tugas yang dilakukan, dll.)." Itu definisi yang lebih menakutkan.
Penjelasan terbaik untuk ini adalah jika Anda memiliki kelas orang tua dan kelas anak, maka kelas dasar dan kelas anak dapat digunakan secara bergantian tanpa mendapatkan hasil yang salah. Ini mungkin masih membingungkan, jadi mari kita lihat contoh persegi persegi klasik. Secara matematis, persegi adalah persegi panjang, tetapi jika Anda memodelkannya menggunakan hubungan "IS-A" melalui warisan, Anda dengan cepat mendapat masalah.
Buruk:
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 ) ;Bagus:
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 ) ;⬆ Kembali ke atas
ISP menyatakan bahwa "klien tidak boleh dipaksa untuk bergantung pada antarmuka yang tidak mereka gunakan."
Contoh yang baik untuk dilihat yang menunjukkan prinsip ini adalah untuk kelas yang membutuhkan objek pengaturan besar. Tidak mengharuskan klien untuk mengatur sejumlah besar opsi bermanfaat, karena sebagian besar waktu mereka tidak akan membutuhkan semua pengaturan. Membuatnya opsional membantu mencegah memiliki "antarmuka lemak".
Buruk:
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
}
}Bagus:
Tidak setiap pekerja adalah karyawan, tetapi setiap karyawan adalah seorang pekerja.
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
}
}⬆ Kembali ke atas
Prinsip ini menyatakan dua hal penting:
Ini bisa sulit dimengerti pada awalnya, tetapi jika Anda telah bekerja dengan .NET/.NET Core Framework, Anda telah melihat implementasi prinsip ini dalam bentuk Injeksi Ketergantungan (DI). Meskipun mereka bukan konsep yang identik, Dip menjaga modul tingkat tinggi dari mengetahui detail modul tingkat rendah dan mengaturnya. Itu dapat mencapai ini melalui DI. Manfaat besar dari ini adalah mengurangi kopling antar modul. Kopling adalah pola pengembangan yang sangat buruk karena membuat kode Anda sulit untuk refactor.
Buruk:
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 ( ) ;
}
}Bagus:
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 ( ) ;
}
}
}⬆ Kembali ke atas
Cobalah untuk mengamati prinsip kering.
Lakukan yang terbaik untuk menghindari kode duplikat. Kode duplikat buruk karena itu berarti ada lebih dari satu tempat untuk mengubah sesuatu jika Anda perlu mengubah beberapa logika.
Bayangkan jika Anda menjalankan restoran dan Anda melacak inventaris Anda: semua tomat, bawang, bawang putih, rempah -rempah, dll. Jika Anda memiliki beberapa daftar yang Anda simpan, maka semua harus diperbarui ketika Anda menyajikan hidangan dengan tomat di dalamnya. Jika Anda hanya memiliki satu daftar, hanya ada satu tempat untuk diperbarui!
Seringkali Anda memiliki kode duplikat karena Anda memiliki dua atau lebih hal yang sedikit berbeda, yang memiliki banyak kesamaan, tetapi perbedaan mereka memaksa Anda untuk memiliki dua atau lebih fungsi terpisah yang melakukan banyak hal yang sama. Menghapus kode duplikat berarti membuat abstraksi yang dapat menangani rangkaian hal yang berbeda ini hanya dengan satu fungsi/modul/kelas.
Mendapatkan abstraksi yang benar sangat penting, itulah sebabnya Anda harus mengikuti prinsip -prinsip padat yang ditetapkan di bagian kelas. Abstraksi yang buruk bisa lebih buruk dari kode duplikat, jadi berhati -hatilah! Setelah mengatakan ini, jika Anda bisa melakukan abstraksi yang baik, lakukanlah! Jangan mengulangi diri sendiri, jika tidak, Anda akan memperbarui beberapa tempat kapan saja Anda ingin mengubah satu hal.
Buruk:
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 ) ;
}
}Bagus:
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 ) ;
}
}Sangat bagus:
Lebih baik menggunakan versi kode yang ringkas.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ Kembali ke atas
Pengujian lebih penting daripada pengiriman. Jika Anda tidak memiliki tes atau jumlah yang tidak memadai, maka setiap kali Anda mengirimkan kode, Anda tidak akan yakin bahwa Anda tidak merusak apa pun. Memutuskan apa yang merupakan jumlah yang memadai terserah tim Anda, tetapi memiliki cakupan 100% (semua pernyataan dan cabang) adalah bagaimana Anda mencapai kepercayaan diri yang sangat tinggi dan ketenangan pikiran. Ini berarti bahwa selain memiliki kerangka kerja pengujian yang hebat, Anda juga perlu menggunakan alat cakupan yang baik.
Tidak ada alasan untuk tidak menulis tes. Ada banyak kerangka kerja tes .NET yang bagus, jadi temukan satu yang lebih disukai tim Anda. Saat Anda menemukan satu yang berfungsi untuk tim Anda, maka bertujuan untuk selalu menulis tes untuk setiap fitur/modul baru yang Anda perkenalkan. Jika metode yang Anda sukai adalah Test Driven Development (TDD), itu bagus, tetapi poin utamanya adalah untuk memastikan Anda mencapai tujuan cakupan Anda sebelum meluncurkan fitur apa pun, atau refactoring yang sudah ada.
Memastikan bahwa tes Anda berfokus pada laser dan tidak menguji hal-hal lain-lain (tidak terkait), memaksa AAA Patern yang digunakan untuk membuat kode Anda lebih bersih dan mudah dibaca.
Buruk:
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 ) ;
}
}Bagus:
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
⬆ Kembali ke atas
Ringkasan Pedoman Pemrograman Asinkron
| Nama | Keterangan | Pengecualian |
|---|---|---|
| Hindari void async | Lebih suka metode tugas async daripada metode void async | Penangan acara |
| Async sepanjang jalan | Jangan mencampur blocking dan kode async | Metode utama konsol (C# <= 7.0) |
| Konfigurasikan konteks | Gunakan ConfigureAwait(false) saat Anda bisa | Metode yang membutuhkan konteks |
Cara async dalam melakukan sesuatu
| Untuk melakukan ini ... | Bukannya ... | Gunakan ini |
|---|---|---|
| Ambil hasil dari tugas latar belakang | Task.Wait or Task.Result | await |
| Tunggu tugas apa pun untuk menyelesaikannya | Task.WaitAny | await Task.WhenAny |
| Ambil hasil beberapa tugas | Task.WaitAll | await Task.WhenAll |
| Tunggu periode waktu | Thread.Sleep | await Task.Delay |
Praktik terbaik
Async/menunggu adalah yang terbaik untuk tugas IO terikat (komunikasi jaringan, komunikasi basis data, permintaan HTTP, dll.) Tetapi tidak baik untuk diterapkan pada tugas terikat komputasi (melintasi pada daftar besar, membuat gambar pelukan, dll.). Karena itu akan melepaskan utas holding ke kumpulan utas dan CPU/core yang tersedia tidak akan melibatkan untuk memproses tugas -tugas tersebut. Oleh karena itu, kita harus menghindari penggunaan async/menunggu untuk tugas terikat secara komputasi.
Untuk menangani tugas -tugas terikat komputasi, LongRunning suka menggunakan Task.Factory.CreateNew TaskCreationOptions Ini akan memulai utas latar belakang baru untuk memproses tugas terikat komputasi yang berat tanpa melepaskannya kembali ke kumpulan utas sampai tugas selesai.
Ketahui alat Anda
Ada banyak yang harus dipelajari tentang async dan menunggu, dan wajar untuk sedikit bingung. Berikut adalah referensi singkat solusi untuk masalah umum.
Solusi untuk masalah async umum
| Masalah | Larutan |
|---|---|
| Buat tugas untuk menjalankan kode | Task.Run atau TaskFactory.StartNew (bukan konstruktor Task atau Task.Start ) |
| Buat pembungkus tugas untuk operasi atau acara | TaskFactory.FromAsync atau TaskCompletionSource<T> |
| Mendukung pembatalan | CancellationTokenSource dan CancellationToken |
| Laporkan kemajuan | IProgress<T> dan Progress<T> |
| Menangani aliran data | TPL Dataflow atau Ekstensi Reaktif |
| Sinkronisasi akses ke sumber daya bersama | SemaphoreSlim |
| Secara tidak sinkron menginisialisasi sumber daya | AsyncLazy<T> |
| Struktur Produsen/Konsumen Siap Async | TPL DataFlow atau AsyncCollection<T> |
Baca dokumen pola asinkron (tap) berbasis tugas. Ini ditulis dengan sangat baik, dan termasuk panduan tentang desain API dan penggunaan async/menunggu yang tepat (termasuk pembatalan dan pelaporan kemajuan).
Ada banyak teknik baru yang ramah-menunggu yang harus digunakan sebagai pengganti teknik pemblokiran lama. Jika Anda memiliki salah satu contoh lama ini dalam kode async baru Anda, Anda melakukan salah (TM):
| Tua | Baru | Keterangan |
|---|---|---|
task.Wait | await task | Tunggu/tunggu tugas diselesaikan |
task.Result | await task | Dapatkan hasil tugas yang diselesaikan |
Task.WaitAny | await Task.WhenAny | Tunggu/tunggu untuk salah satu kumpulan tugas untuk diselesaikan |
Task.WaitAll | await Task.WhenAll | Tunggu/tunggu untuk setiap kumpulan tugas untuk diselesaikan |
Thread.Sleep | await Task.Delay | Tunggu/tunggu untuk jangka waktu tertentu |
Konstruktor Task | Task.Run atau TaskFactory.StartNew | Buat tugas berbasis kode |
Sumber https://gist.github.com/jonlabelle/841146854b23b305b50fa5542f84b20c
⬆ Kembali ke atas
Kesalahan yang dilemparkan adalah hal yang baik! Maksudnya, runtime telah berhasil mengidentifikasi ketika sesuatu dalam program Anda salah dan memberi tahu Anda dengan menghentikan eksekusi fungsi pada tumpukan saat ini, membunuh proses (di .net/.net core), dan memberi tahu Anda di konsol dengan jejak tumpukan.
Jika Anda perlu melemparkan kembali pengecualian setelah menangkapnya, gunakan saja 'lempar' dengan menggunakan ini, Anda akan menyimpan jejak tumpukan. Tetapi dalam opsi buruk di bawah ini, Anda akan kehilangan jejak tumpukan.
Buruk:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ex ;
}Bagus:
try
{
// Do something..
}
catch ( Exception ex )
{
// Any action something like roll-back or logging etc.
throw ;
}⬆ Kembali ke atas
Tidak melakukan apa pun dengan kesalahan yang tertangkap tidak memberi Anda kemampuan untuk memperbaiki atau bereaksi terhadap kesalahan tersebut. Melempar kesalahan tidak jauh lebih baik karena sering kali bisa hilang dalam lautan yang dicetak ke konsol. Jika Anda membungkus sedikit kode dalam try/catch itu berarti Anda berpikir kesalahan dapat terjadi di sana dan oleh karena itu Anda harus memiliki rencana, atau membuat jalur kode, untuk saat itu terjadi.
Buruk:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
// silent exception
}Bagus:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
NotifyUserOfError ( error ) ;
// Another option
ReportErrorToService ( error ) ;
}⬆ Kembali ke atas
Jika Anda perlu mengambil tindakan sesuai dengan jenis pengecualian, Anda lebih baik menggunakan beberapa blok tangkapan untuk penanganan pengecualian.
Buruk:
try
{
// Do something..
}
catch ( Exception ex )
{
if ( ex is TaskCanceledException )
{
// Take action for TaskCanceledException
}
else if ( ex is TaskSchedulerException )
{
// Take action for TaskSchedulerException
}
}Bagus:
try
{
// Do something..
}
catch ( TaskCanceledException ex )
{
// Take action for TaskCanceledException
}
catch ( TaskSchedulerException ex )
{
// Take action for TaskSchedulerException
}⬆ Kembali ke atas
C# memungkinkan pengecualian untuk diputar ulang di blok tangkapan menggunakan kata kunci throw . Ini adalah praktik yang buruk untuk melempar pengecualian yang tertangkap menggunakan throw e; . Pernyataan ini mengatur ulang jejak tumpukan. Alih -alih gunakan throw; . Ini akan menjaga jejak tumpukan dan memberikan wawasan yang lebih dalam tentang pengecualian. Pilihan lain adalah menggunakan pengecualian khusus. Cukup instantiate pengecualian baru dan atur properti pengecualian batinnya ke pengecualian yang tertangkap dengan melempar new CustomException("some info", e); . Menambahkan informasi ke pengecualian adalah praktik yang baik karena akan membantu dengan debugging. Namun, jika tujuannya adalah untuk mencatat pengecualian maka gunakan throw; untuk meneruskan uang ke penelepon.
Buruk:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception ex )
{
logger . LogInfo ( ex ) ;
throw ex ;
}Bagus:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw ;
}Bagus:
try
{
FunctionThatMightThrow ( ) ;
}
catch ( Exception error )
{
logger . LogInfo ( error ) ;
throw new CustomException ( error ) ;
}⬆ Kembali ke atas
Buruk:
Memiliki banyak gaya pemformatan kode dalam proyek. Misalnya, gaya inden adalah space dan tab yang dicampur dalam proyek.
Bagus:
Tentukan dan pertahankan gaya kode yang konsisten di basis kode Anda dengan menggunakan file .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⬆ Kembali ke atas
Mereka biasanya hanya menambahkan kebisingan. Biarkan fungsi dan nama variabel bersama dengan lekukan dan pemformatan yang tepat berikan struktur visual pada kode Anda.
Buruk:
////////////////////////////////////////////////////////////////////////////////
// Scope Model Instantiation
////////////////////////////////////////////////////////////////////////////////
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
////////////////////////////////////////////////////////////////////////////////
// Action setup
////////////////////////////////////////////////////////////////////////////////
void Actions ( )
{
// ...
} ;Buruk:
#region Scope Model Instantiation
var model = {
menu : 'foo' ,
nav : 'bar'
} ;
#endregion
#region Action setup
void Actions ( ) {
// ...
} ;
#endregionBagus:
var model = new [ ]
{
menu : 'foo' ,
nav : 'bar'
} ;
void Actions ( )
{
// ...
} ;⬆ Kembali ke atas
Kontrol versi ada karena suatu alasan. Tinggalkan kode lama dalam sejarah Anda.
Buruk:
doStuff ( ) ;
// doOtherStuff();
// doSomeMoreStuff();
// doSoMuchStuff();Bagus:
doStuff ( ) ;⬆ Kembali ke atas
Ingat, gunakan kontrol versi! Tidak perlu kode mati, kode berkomentar, dan terutama komentar jurnal. Gunakan git log untuk mendapatkan sejarah!
Buruk:
/**
* 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 ;
}Bagus:
public int Combine ( int a , int b )
{
return a + b ;
}⬆ Kembali ke atas
Komentar adalah permintaan maaf, bukan persyaratan. Kode bagus sebagian besar mendokumentasikan itu sendiri.
Buruk:
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 ;
}
}Lebih baik tapi tetap buruk:
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 ;
}
} Jika komentar menjelaskan apa yang dilakukan kode, itu mungkin komentar yang tidak berguna dan dapat diimplementasikan dengan variabel atau fungsi yang dinamai dengan baik. Komentar dalam kode sebelumnya dapat diganti dengan fungsi bernama ConvertTo32bitInt sehingga komentar ini masih tidak berguna. Namun akan sulit untuk diekspresikan dengan kode mengapa pengembang memilih algoritma hash DJB2 alih-alih SHA-1 atau fungsi hash lainnya. Dalam hal ini komentar dapat diterima.
Bagus:
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 ;
}⬆ Kembali ke atas
Terima kasih untuk semua orang yang telah berkontribusi pada proyek clean-code-dotnet
Cintai pekerjaan kami dan bantu kami melanjutkan kegiatan kami? [Menjadi pendukung]
Jadilah sponsor dan ambil logo Anda di readme kami di github dengan tautan ke situs Anda. [Menjadi sponsor]
Sejauh mungkin berdasarkan hukum, Thangchung telah melepaskan semua hak cipta dan hak terkait atau tetangga untuk pekerjaan ini.