إذا كنت تحب مشروع clean-code-dotnet أو إذا ساعدك ، فيرجى إعطاء نجمة لهذا المستودع. لن يساعد ذلك فقط في تعزيز مجتمع .NET ولكن أيضًا تحسين المهارات المتعلقة بالرمز النظيف لمطوري .NET في جميع أنحاء العالم. شكراً جزيلاً ؟
تحقق من مدونتي أو قل مرحبًا على Twitter!
مبادئ هندسة البرمجيات ، من كتاب Clean Book Robert C. Martin ، تم تكييفه لـ .NET/.NET Core. هذا ليس دليل نمط. إنه دليل لإنتاج برامج قابلة للقراءة وقابلة لإعادة الاستخدام وقابلة لإعادة الاستخدام في .NET/.NET Core.
لا يجب اتباع كل مبدأ هنا بشكل صارم ، وحتى أقل من ذلك سيتم الاتفاق عليها عالميا. هذه هي إرشادات ولا شيء أكثر من ذلك ، لكنها تم تدوينها على مدار سنوات عديدة من الخبرة الجماعية من قبل مؤلفي التعليمات البرمجية النظيفة .
مستوحاة من قوائم Clean-Code-JavaScript وقوائم Clean-Code-PHP.
سيء:
int d ;جيد:
int daySinceModification ;⬆ العودة إلى الأعلى
قم بتسمية المتغير لتعكس ما يتم استخدامه.
سيء:
var dataFromDb = db . GetFromService ( ) . ToList ( ) ;جيد:
var listOfEmployee = _employeeService . GetEmployees ( ) . ToList ( ) ;⬆ العودة إلى الأعلى
يعيد الترميز المجري النوع الذي يوجد بالفعل في الإعلان. هذا لا معنى له لأن IDEs الحديثة ستحدد النوع.
سيء:
int iCounter ;
string strFullName ;
DateTime dModifiedDate ;جيد:
int counter ;
string fullName ;
DateTime modifiedDate ;كما يجب أن لا يتم استخدام التدوين المجري في المعالم.
سيء:
public bool IsShopOpen ( string pDay , int pAmount )
{
// some logic
}جيد:
public bool IsShopOpen ( string day , int amount )
{
// some logic
}⬆ العودة إلى الأعلى
تخبرك الرسملة بالكثير عن متغيراتك ، وظائف ، وما إلى ذلك. هذه القواعد ذاتية ، حتى يتمكن فريقك من اختيار ما يريدون. النقطة المهمة ، بغض النظر عن ما تختاره جميعًا ، فقط كن متسقًا.
سيء:
const int DAYS_IN_WEEK = 7 ;
const int daysInMonth = 30 ;
var songs = new List < string > { 'Back In Black' , 'Stairway to Heaven' , 'Hey Jude' } ;
var Artists = new List < string > { 'ACDC' , 'Led Zeppelin' , 'The Beatles' } ;
bool EraseDatabase ( ) { }
bool Restore_database ( ) { }
class animal { }
class Alpaca { }جيد:
const int DaysInWeek = 7 ;
const int DaysInMonth = 30 ;
var songs = new List < string > { 'Back In Black' , 'Stairway to Heaven' , 'Hey Jude' } ;
var artists = new List < string > { 'ACDC' , 'Led Zeppelin' , 'The Beatles' } ;
bool EraseDatabase ( ) { }
bool RestoreDatabase ( ) { }
class Animal { }
class Alpaca { }⬆ العودة إلى الأعلى
سيستغرق الأمر بعض الوقت للتحقيق في معنى المتغيرات والوظائف عندما تكون غير واضحة.
سيء:
public class Employee
{
public Datetime sWorkDate { get ; set ; } // what the heck is this
public Datetime modTime { get ; set ; } // same here
}جيد:
public class Employee
{
public Datetime StartWorkingDate { get ; set ; }
public Datetime ModificationTime { get ; set ; }
}⬆ العودة إلى الأعلى
استخدم تدوين CAMELCASE للمتغير والطريقة.
سيء:
var employeephone ;
public double CalculateSalary ( int workingdays , int workinghours )
{
// some logic
}جيد:
var employeePhone ;
public double CalculateSalary ( int workingDays , int workingHours )
{
// some logic
}⬆ العودة إلى الأعلى
الأشخاص الذين يقرؤون الكود الخاص بك هم أيضًا مبرمجون. تسمية الأشياء بشكل صحيح سيساعد الجميع على أن يكونوا على نفس الصفحة. لا نريد أن نأخذ وقتًا لشرح للجميع ما هو متغير أو وظيفة.
جيد
public class SingleObject
{
// create an object of SingleObject
private static SingleObject _instance = new SingleObject ( ) ;
// make the constructor private so that this class cannot be instantiated
private SingleObject ( ) { }
// get the only object available
public static SingleObject GetInstance ( )
{
return _instance ;
}
public string ShowMessage ( )
{
return "Hello World!" ;
}
}
public static void main ( String [ ] args )
{
// illegal construct
// var object = new SingleObject();
// Get the only object available
var singletonObject = SingleObject . GetInstance ( ) ;
// show the message
singletonObject . ShowMessage ( ) ;
}⬆ العودة إلى الأعلى
الكثير إذا كانت العبارات الأخرى يمكن أن تجعل الكود يصعب متابعته. صريح أفضل من الضمني .
سيء:
public bool IsShopOpen ( string day )
{
if ( ! string . IsNullOrEmpty ( day ) )
{
day = day . ToLower ( ) ;
if ( day == "friday" )
{
return true ;
}
else if ( day == "saturday" )
{
return true ;
}
else if ( day == "sunday" )
{
return true ;
}
else
{
return false ;
}
}
else
{
return false ;
}
}جيد:
public bool IsShopOpen ( string day )
{
if ( string . IsNullOrEmpty ( day ) )
{
return false ;
}
var openingDays = new [ ] { "friday" , "saturday" , "sunday" } ;
return openingDays . Any ( d => d == day . ToLower ( ) ) ;
}سيء:
public long Fibonacci ( int n )
{
if ( n < 50 )
{
if ( n != 0 )
{
if ( n != 1 )
{
return Fibonacci ( n - 1 ) + Fibonacci ( n - 2 ) ;
}
else
{
return 1 ;
}
}
else
{
return 0 ;
}
}
else
{
throw new System . Exception ( "Not supported" ) ;
}
}جيد:
public long Fibonacci ( int n )
{
if ( n == 0 )
{
return 0 ;
}
if ( n == 1 )
{
return 1 ;
}
if ( n > 50 )
{
throw new System . Exception ( "Not supported" ) ;
}
return Fibonacci ( n - 1 ) + Fibonacci ( n - 2 ) ;
}⬆ العودة إلى الأعلى
لا تجبر قارئ الكود على ترجمة ما يعنيه المتغير. صريح أفضل من الضمني .
سيء:
var l = new [ ] { "Austin" , "New York" , "San Francisco" } ;
for ( var i = 0 ; i < l . Count ( ) ; i ++ )
{
var li = l [ i ] ;
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
// Wait, what is `li` for again?
Dispatch ( li ) ;
}جيد:
var locations = new [ ] { "Austin" , "New York" , "San Francisco" } ;
foreach ( var location in locations )
{
DoStuff ( ) ;
DoSomeOtherStuff ( ) ;
// ...
// ...
// ...
Dispatch ( location ) ;
}⬆ العودة إلى الأعلى
السلاسل السحرية هي قيم سلسلة محددة مباشرة ضمن رمز التطبيق والتي لها تأثير على سلوك التطبيق. في كثير من الأحيان ، ستنتهي هذه الأوتار بتكرارها داخل النظام ، وبما أنه لا يمكن تحديثها تلقائيًا باستخدام أدوات إعادة الطرد ، فإنها تصبح مصدرًا شائعًا للحشرات عند إجراء تغييرات على بعض الأوتار ولكن ليس غيرها.
سيء
if ( userRole == "Admin" )
{
// logic in here
}جيد
const string ADMIN_ROLE = "Admin"
if ( userRole == ADMIN_ROLE )
{
// logic in here
}باستخدام هذا ، علينا فقط التغيير في Centerize Place والبعض الآخر سيؤدي تكييفه.
⬆ العودة إلى الأعلى
إذا أخبرك اسم الفصل/الكائن بشيء ما ، فلا تكرر ذلك في اسمك المتغير.
سيء:
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 عن طريق تسمية 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 ) ;
}⬆ العودة إلى الأعلى
ليس جيدا:
هذا ليس جيدًا لأن breweryName يمكن أن يكون NULL .
هذا الرأي أكثر فهمًا من الإصدار السابق ، لكنه يتحكم بشكل أفضل في قيمة المتغير.
public void CreateMicrobrewery ( string name = null )
{
var breweryName = ! string . IsNullOrEmpty ( name ) ? name : "Hipster Brew Co." ;
// ...
}جيد:
public void CreateMicrobrewery ( string breweryName = "Hipster Brew Co." )
{
// ...
}⬆ العودة إلى الأعلى
تنتج الوظيفة تأثيرًا جانبيًا إذا قامت بأي شيء آخر غير أخذ قيمة وإرجاع قيمة أو قيم أخرى. يمكن أن يكون التأثير الجانبي يكتب إلى ملف ، أو تعديل بعض المتغيرات العالمية ، أو توصيل كل أموالك عن طريق الخطأ إلى شخص غريب.
الآن ، تحتاج إلى وجود آثار جانبية في البرنامج في بعض الأحيان. مثل المثال السابق ، قد تحتاج إلى الكتابة إلى ملف. ما تريد القيام به هو مركزية المكان الذي تقوم به. ليس لديك العديد من الوظائف والفئات التي تكتب إلى ملف معين. لديك خدمة واحدة تفعل ذلك. واحد وواحد فقط.
النقطة الرئيسية هي تجنب المزالق الشائعة مثل مشاركة الحالة بين الكائنات دون أي بنية ، وذلك باستخدام أنواع البيانات القابلة للتغيير التي يمكن كتابتها بواسطة أي شيء ، وليس مركزية حيث تحدث آثارك الجانبية. إذا كنت تستطيع القيام بذلك ، فستكون أكثر سعادة من الغالبية العظمى من المبرمجين الآخرين.
سيء:
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
var name = "Ryan McDermott" ;
public void SplitAndEnrichFullName ( )
{
var temp = name . Split ( " " ) ;
name = $ "His first name is { temp [ 0 ] } , and his last name is { temp [ 1 ] } " ; // side effect
}
SplitAndEnrichFullName ( ) ;
Console . WriteLine ( name ) ; // His first name is Ryan, and his last name is McDermottجيد:
public string SplitAndEnrichFullName ( string name )
{
var temp = name . Split ( " " ) ;
return $ "His first name is { temp [ 0 ] } , and his last name is { temp [ 1 ] } " ;
}
var name = "Ryan McDermott" ;
var fullName = SplitAndEnrichFullName ( name ) ;
Console . WriteLine ( name ) ; // Ryan McDermott
Console . WriteLine ( fullName ) ; // His first name is Ryan, and his last name is McDermott⬆ العودة إلى الأعلى
سيء:
public bool IsDOMNodeNotPresent ( string node )
{
// ...
}
if ( ! IsDOMNodeNotPresent ( node ) )
{
// ...
}جيد:
public bool IsDOMNodePresent ( string node )
{
// ...
}
if ( IsDOMNodePresent ( node ) )
{
// ...
}⬆ العودة إلى الأعلى
هذا يبدو وكأنه مهمة مستحيلة. عند سماع هذا أولاً ، يقول معظم الناس ، "كيف من المفترض أن أفعل أي شيء بدون بيان if ؟" الجواب هو أنه يمكنك استخدام تعدد الأشكال لتحقيق نفس المهمة في كثير من الحالات. السؤال الثاني هو عادة ، "حسنًا ، هذا رائع ولكن لماذا أريد أن أفعل ذلك؟" الجواب هو مفهوم رمز نظيف سابق تعلمنه: يجب أن تفعل الوظيفة شيئًا واحدًا فقط. عندما يكون لديك فئات ووظائف تحتوي if بيانات ، فأنت تخبر المستخدم بأن وظيفتك تقوم بأكثر من شيء واحد. تذكر ، فقط افعل شيئًا واحدًا.
سيء:
class Airplane
{
// ...
public double GetCruisingAltitude ( )
{
switch ( _type )
{
case '7 77 ' :
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
case 'Air Force One' :
return GetMaxAltitude ( ) ;
case 'Cessna' :
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}
}جيد:
interface IAirplane
{
// ...
double GetCruisingAltitude ( ) ;
}
class Boeing777 : IAirplane
{
// ...
public double GetCruisingAltitude ( )
{
return GetMaxAltitude ( ) - GetPassengerCount ( ) ;
}
}
class AirForceOne : IAirplane
{
// ...
public double GetCruisingAltitude ( )
{
return GetMaxAltitude ( ) ;
}
}
class Cessna : IAirplane
{
// ...
public double GetCruisingAltitude ( )
{
return GetMaxAltitude ( ) - GetFuelExpenditure ( ) ;
}
}⬆ العودة إلى الأعلى
سيء:
public Path TravelToTexas ( object vehicle )
{
if ( vehicle . GetType ( ) == typeof ( Bicycle ) )
{
( vehicle as Bicycle ) . PeddleTo ( new Location ( "texas" ) ) ;
}
else if ( vehicle . GetType ( ) == typeof ( Car ) )
{
( vehicle as Car ) . DriveTo ( new Location ( "texas" ) ) ;
}
}جيد:
public Path TravelToTexas ( Traveler vehicle )
{
vehicle . TravelTo ( new Location ( "texas" ) ) ;
}أو
// pattern matching
public Path TravelToTexas ( object vehicle )
{
if ( vehicle is Bicycle bicycle )
{
bicycle . PeddleTo ( new Location ( "texas" ) ) ;
}
else if ( vehicle is Car car )
{
car . DriveTo ( new Location ( "texas" ) ) ;
}
}⬆ العودة إلى الأعلى
سيء:
public int Combine ( dynamic val1 , dynamic val2 )
{
int value ;
if ( ! int . TryParse ( val1 , out value ) || ! int . TryParse ( val2 , out value ) )
{
throw new Exception ( 'Must be of type Number' ) ;
}
return val1 + val2 ;
}جيد:
public int Combine ( int val1 , int val2 )
{
return val1 + val2 ;
}⬆ العودة إلى الأعلى
يشير العلم إلى أن الطريقة تتحمل أكثر من مسؤولية واحدة. من الأفضل أن تتحمل الطريقة مسؤولية واحدة فقط. تقسيم الطريقة إلى اثنين إذا كانت المعلمة المنطقية تضيف مسؤوليات متعددة إلى الطريقة.
سيء:
public void CreateFile ( string name , bool temp = false )
{
if ( temp )
{
Touch ( "./temp/" + name ) ;
}
else
{
Touch ( name ) ;
}
}جيد:
public void CreateFile ( string name )
{
Touch ( name ) ;
}
public void CreateTempFile ( string name )
{
Touch ( "./temp/" + name ) ;
}⬆ العودة إلى الأعلى
تعتبر ملوثات Globals ممارسة سيئة في العديد من اللغات لأنه يمكن أن تصطدم بمكتبة أخرى وسيكون مستخدم واجهة برمجة التطبيقات الخاصة بك لا شيء حتى يحصل على استثناء في الإنتاج. دعونا نفكر في مثال: ماذا لو كنت تريد الحصول على مجموعة تكوين. يمكنك كتابة وظيفة عالمية مثل 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 في التطبيق الخاص بك.
⬆ العودة إلى الأعلى
Singleton هو مضاد للنمط. إعادة صياغة من 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 في التطبيق الخاص بك.
⬆ العودة إلى الأعلى
يعد الحد من مقدار معلمات الوظيفة أمرًا مهمًا للغاية لأنه يجعل اختبار وظيفتك أسهل. إن وجود أكثر من ثلاثة يؤدي إلى انفجار توافقي حيث يتعين عليك اختبار الكثير من الحالات المختلفة مع كل حجة منفصلة.
حجج الصفر هي الحالة المثالية. حجة واحدة أو اثنتين على ما يرام ، ويجب تجنب ثلاثة. أي شيء أكثر من ذلك ينبغي توحيده. عادة ، إذا كان لديك أكثر من وسيطين ، فإن وظيفتك تحاول أن تفعل الكثير. في الحالات التي لا تكون فيها ، في معظم الأحيان ، سيكون كائن المستوى الأعلى كافيًا كوسيطة.
سيء:
public void CreateMenu ( string title , string body , string buttonText , bool cancellable )
{
// ...
}جيد:
public class MenuConfig
{
public string Title { get ; set ; }
public string Body { get ; set ; }
public string ButtonText { get ; set ; }
public bool Cancellable { get ; set ; }
}
var config = new MenuConfig
{
Title = "Foo" ,
Body = "Bar" ,
ButtonText = "Baz" ,
Cancellable = true
} ;
public void CreateMenu ( MenuConfig config )
{
// ...
}⬆ العودة إلى الأعلى
هذه هي القاعدة الأكثر أهمية في هندسة البرمجيات. عندما تفعل الوظائف أكثر من شيء واحد ، فمن الصعب إنشاء واختبار وعقل. عندما تتمكن من عزل وظيفة لإجراء واحد فقط ، يمكن إعادة تمهيدها بسهولة وسيقرأ الكود الخاص بك أنظف كثيرًا. إذا لم تأخذ أي شيء آخر بعيدًا عن هذا الدليل بخلاف هذا ، فستكون متقدمًا على العديد من المطورين.
سيء:
public void SendEmailToListOfClients ( string [ ] clients )
{
foreach ( var client in clients )
{
var clientRecord = db . Find ( client ) ;
if ( clientRecord . IsActive ( ) )
{
Email ( client ) ;
}
}
}جيد:
public void SendEmailToListOfClients ( string [ ] clients )
{
var activeClients = GetActiveClients ( clients ) ;
// Do some logic
}
public List < Client > GetActiveClients ( string [ ] clients )
{
return db . Find ( clients ) . Where ( s => s . Status == "Active" ) ;
}⬆ العودة إلى الأعلى
سيء:
public class Email
{
//...
public void Handle ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// What is this? A handle for the message? Are we writing to a file now?
message . Handle ( ) ;جيد:
public class Email
{
//...
public void Send ( )
{
SendMail ( this . _to , this . _subject , this . _body ) ;
}
}
var message = new Email ( .. . ) ;
// Clear and obvious
message . Send ( ) ;⬆ العودة إلى الأعلى
لم ينته بعد
عندما يكون لديك أكثر من مستوى واحد من التجريد ، فإن وظيفتك عادة ما تفعل الكثير. تقسيم الوظائف يؤدي إلى إعادة الاستخدام وأسهل الاختبار.
سيء:
public string ParseBetterJSAlternative ( string code )
{
var regexes = [
// ...
] ;
var statements = explode ( " " , code ) ;
var tokens = new string [ ] { } ;
foreach ( var regex in regexes )
{
foreach ( var statement in statements )
{
// ...
}
}
var ast = new string [ ] { } ;
foreach ( var token in tokens )
{
// lex...
}
foreach ( var node in ast )
{
// parse...
}
}سيء أيضًا:
لقد نفذنا بعض الوظائف ، لكن وظيفة ParseBetterJSAlternative() لا تزال معقدة للغاية وغير قابلة للاختبار.
public string Tokenize ( string code )
{
var regexes = new string [ ]
{
// ...
} ;
var statements = explode ( " " , code ) ;
var tokens = new string [ ] { } ;
foreach ( var regex in regexes )
{
foreach ( var statement in statements )
{
tokens [ ] = /* ... */ ;
}
}
return tokens ;
}
public string Lexer ( string [ ] tokens )
{
var ast = new string [ ] { } ;
foreach ( var token in tokens )
{
ast [ ] = /* ... */ ;
}
return ast ;
}
public string ParseBetterJSAlternative ( string code )
{
var tokens = Tokenize ( code ) ;
var ast = Lexer ( tokens ) ;
foreach ( var node in ast )
{
// parse...
}
}جيد:
أفضل حل هو الخروج من تبعيات وظيفة ParseBetterJSAlternative() .
class Tokenizer
{
public string Tokenize ( string code )
{
var regexes = new string [ ] {
// ...
} ;
var statements = explode ( " " , code ) ;
var tokens = new string [ ] { } ;
foreach ( var regex in regexes )
{
foreach ( var statement in statements )
{
tokens [ ] = /* ... */ ;
}
}
return tokens ;
}
}
class Lexer
{
public string Lexify ( string [ ] tokens )
{
var ast = new [ ] { } ;
foreach ( var token in tokens )
{
ast [ ] = /* ... */ ;
}
return ast ;
}
}
class BetterJSAlternative
{
private string _tokenizer ;
private string _lexer ;
public BetterJSAlternative ( Tokenizer tokenizer , Lexer lexer )
{
_tokenizer = tokenizer ;
_lexer = lexer ;
}
public string Parse ( string code )
{
var tokens = _tokenizer . Tokenize ( code ) ;
var ast = _lexer . Lexify ( tokens ) ;
foreach ( var node in ast )
{
// parse...
}
}
}⬆ العودة إلى الأعلى
إذا استدعت وظيفة واحدة أخرى ، فاحرص على إغلاق هذه الوظائف رأسياً في الملف المصدر. من الناحية المثالية ، حافظ على المتصل مباشرة فوق Callee. نميل إلى قراءة الكود من أعلى إلى أسفل ، مثل الصحيفة. لهذا السبب ، اجعل الكود الخاص بك يقرأ بهذه الطريقة.
سيء:
class PerformanceReview
{
private readonly Employee _employee ;
public PerformanceReview ( Employee employee )
{
_employee = employee ;
}
private IEnumerable < PeersData > LookupPeers ( )
{
return db . lookup ( _employee , 'peers' ) ;
}
private ManagerData LookupManager ( )
{
return db . lookup ( _employee , 'manager' ) ;
}
private IEnumerable < PeerReviews > GetPeerReviews ( )
{
var peers = LookupPeers ( ) ;
// ...
}
public PerfReviewData PerfReview ( )
{
GetPeerReviews ( ) ;
GetManagerReview ( ) ;
GetSelfReview ( ) ;
}
public ManagerData GetManagerReview ( )
{
var manager = LookupManager ( ) ;
}
public EmployeeData GetSelfReview ( )
{
// ...
}
}
var review = new PerformanceReview ( employee ) ;
review . PerfReview ( ) ;جيد:
class PerformanceReview
{
private readonly Employee _employee ;
public PerformanceReview ( Employee employee )
{
_employee = employee ;
}
public PerfReviewData PerfReview ( )
{
GetPeerReviews ( ) ;
GetManagerReview ( ) ;
GetSelfReview ( ) ;
}
private IEnumerable < PeerReviews > GetPeerReviews ( )
{
var peers = LookupPeers ( ) ;
// ...
}
private IEnumerable < PeersData > LookupPeers ( )
{
return db . lookup ( _employee , 'peers' ) ;
}
private ManagerData GetManagerReview ( )
{
var manager = LookupManager ( ) ;
return manager ;
}
private ManagerData LookupManager ( )
{
return db . lookup ( _employee , 'manager' ) ;
}
private EmployeeData GetSelfReview ( )
{
// ...
}
}
var review = new PerformanceReview ( employee ) ;
review . PerfReview ( ) ;⬆ العودة إلى الأعلى
سيء:
if ( article . state == "published" )
{
// ...
}جيد:
if ( article . IsPublished ( ) )
{
// ...
}⬆ العودة إلى الأعلى
الكود الميت هو بنفس القدر من الرمز المكرر. لا يوجد سبب للحفاظ عليه في قاعدة الشفرة الخاصة بك. إذا لم يتم استدعاؤه ، تخلص منه! سيظل آمنًا في سجل الإصدار الخاص بك إذا كنت لا تزال بحاجة إليه.
سيء:
public void OldRequestModule ( string url )
{
// ...
}
public void NewRequestModule ( string url )
{
// ...
}
var request = NewRequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;جيد:
public void RequestModule ( string url )
{
// ...
}
var request = RequestModule ( requestUrl ) ;
InventoryTracker ( "apples" , request , "www.inventory-awesome.io" ) ;⬆ العودة إلى الأعلى
في C# / VB.NET ، يمكنك تعيين كلمات رئيسية public protected private للطرق. باستخدامه ، يمكنك التحكم في تعديل الخصائص على كائن.
set .بالإضافة إلى ذلك ، هذا جزء من مبدأ المفتوح/المغلق ، من مبادئ التصميم الموجهة للكائنات.
سيء:
class BankAccount
{
public double Balance = 1000 ;
}
var bankAccount = new BankAccount ( ) ;
// Fake buy shoes...
bankAccount . Balance -= 100 ;جيد:
class BankAccount
{
private double _balance = 0.0D ;
pubic double Balance {
get {
return _balance ;
}
}
public BankAccount ( balance = 1000 )
{
_balance = balance ;
}
public void WithdrawBalance ( int amount )
{
if ( amount > _balance )
{
throw new Exception ( 'Amount greater than available balance . ' ) ;
}
_balance -= amount ;
}
public void DepositBalance ( int amount )
{
_balance += amount ;
}
}
var bankAccount = new BankAccount ( ) ;
// Buy shoes...
bankAccount . WithdrawBalance ( price ) ;
// Get balance
balance = bankAccount . Balance ;⬆ العودة إلى الأعلى
سيء:
class Employee
{
public string Name { get ; set ; }
public Employee ( string name )
{
Name = name ;
}
}
var employee = new Employee ( "John Doe" ) ;
Console . WriteLine ( employee . Name ) ; // Employee name: John Doeجيد:
class Employee
{
public string Name { get ; }
public Employee ( string name )
{
Name = name ;
}
}
var employee = new Employee ( "John Doe" ) ;
Console . WriteLine ( employee . Name ) ; // Employee name: John Doe⬆ العودة إلى الأعلى
هذا النمط مفيد للغاية ويستخدم عادة في العديد من المكتبات. إنها تسمح للمعبرة عن الكود الخاص بك ، وأقل مطولاً. لهذا السبب ، استخدم طريقة التسلسل وألقي نظرة على كيفية تنظيف التعليمات البرمجية الخاصة بك.
جيد:
public static class ListExtensions
{
public static List < T > FluentAdd < T > ( this List < T > list , T item )
{
list . Add ( item ) ;
return list ;
}
public static List < T > FluentClear < T > ( this List < T > list )
{
list . Clear ( ) ;
return list ;
}
public static List < T > FluentForEach < T > ( this List < T > list , Action < T > action )
{
list . ForEach ( action ) ;
return list ;
}
public static List < T > FluentInsert < T > ( this List < T > list , int index , T item )
{
list . Insert ( index , item ) ;
return list ;
}
public static List < T > FluentRemoveAt < T > ( this List < T > list , int index )
{
list . RemoveAt ( index ) ;
return list ;
}
public static List < T > FluentReverse < T > ( this List < T > list )
{
list . Reverse ( ) ;
return list ;
}
}
internal static void ListFluentExtensions ( )
{
var list = new List < int > ( ) { 1 , 2 , 3 , 4 , 5 }
. FluentAdd ( 1 )
. FluentInsert ( 0 , 0 )
. FluentRemoveAt ( 1 )
. FluentReverse ( )
. FluentForEach ( value => value . WriteLine ( ) )
. FluentClear ( ) ;
}⬆ العودة إلى الأعلى
كما هو مذكور في أنماط التصميم من قبل عصابة أربعة ، يجب أن تفضل التكوين على الميراث حيث يمكنك. هناك الكثير من الأسباب الجيدة لاستخدام الميراث والكثير من الأسباب الجيدة لاستخدام التكوين.
النقطة الرئيسية لهذا الحد الأقصى هي أنه إذا كان عقلك غريزيًا للميراث ، فحاول التفكير إذا كان التكوين يمكن أن يصمم مشكلتك بشكل أفضل. في بعض الحالات يمكن أن.
قد تتساءل بعد ذلك ، "متى يجب أن أستخدم الميراث؟" يعتمد ذلك على مشكلتك في متناول اليد ، ولكن هذه قائمة لائقة عندما يكون الميراث أكثر منطقية من التكوين:
سيء:
class Employee
{
private string Name { get ; set ; }
private string Email { get ; set ; }
public Employee ( string name , string email )
{
Name = name ;
Email = email ;
}
// ...
}
// Bad because Employees "have" tax data.
// EmployeeTaxData is not a type of Employee
class EmployeeTaxData : Employee
{
private string Name { get ; }
private string Email { get ; }
public EmployeeTaxData ( string name , string email , string ssn , string salary )
{
// ...
}
// ...
}جيد:
class EmployeeTaxData
{
public string Ssn { get ; }
public string Salary { get ; }
public EmployeeTaxData ( string ssn , string salary )
{
Ssn = ssn ;
Salary = salary ;
}
// ...
}
class Employee
{
public string Name { get ; }
public string Email { get ; }
public EmployeeTaxData TaxData { get ; }
public Employee ( string name , string email )
{
Name = name ;
Email = email ;
}
public void SetTax ( string ssn , double salary )
{
TaxData = new EmployeeTaxData ( ssn , salary ) ;
}
// ...
}⬆ العودة إلى الأعلى
Solid هو الاختصار ذاكري الذي قدمه مايكل Feathers للمبادئ الخمسة الأولى التي أطلق عليها روبرت مارتن ، مما يعني خمس مبادئ أساسية للبرمجة والتصميم الموجهة للكائنات.
كما هو مذكور في التعليمات البرمجية النظيفة ، "يجب ألا يكون هناك أبدًا أكثر من سبب لتغيير الفصل". من المغري أن تربط فئة مع الكثير من الوظائف ، مثل عندما يمكنك فقط أخذ حقيبة واحدة في رحلتك. المشكلة في ذلك هي أن صفك لن يكون متماسكًا من الناحية المفاهيمية وسيعطيه العديد من الأسباب للتغيير. من المهم تقليل مقدار المرات التي تحتاج إلى تغييرها.
من المهم لأنه إذا كانت هناك الكثير من الوظائف في فصل واحد وقمت بتعديل جزء منها ، فقد يكون من الصعب فهم كيف سيؤثر ذلك على الوحدات النمطية الأخرى المعتمدة في قاعدة قاعدة الشفرة الخاصة بك.
سيء:
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 بكائنات من النوع S (أي ، قد تحل كائنات النوع S إلى استبدال كائنات من النوع T) دون تغيير أي من الخصائص المرغوبة لهذا البرنامج (الصحة ، المهمة التي يتم تنفيذها ، إلخ)." هذا تعريف أكثر رعبا.
أفضل تفسير لذلك هو إذا كان لديك فئة من الوالدين وفئة طفل ، فيمكن استخدام فئة الأساس وفئة الطفل بالتبادل دون الحصول على نتائج غير صحيحة. قد لا يزال هذا مربكًا ، لذلك دعونا نلقي نظرة على مثال الزنابق الكلاسيكية المربعة. من الناحية الرياضية ، يكون المربع مستطيلًا ، ولكن إذا قمت بتصميمه باستخدام علاقة "IS-A" عبر الميراث ، فستواجه مشكلة بسرعة.
سيء:
class Rectangle
{
protected double Width = 0 ;
protected double Height = 0 ;
public Drawable Render ( double area )
{
// ...
}
public void SetWidth ( double width )
{
Width = width ;
}
public void SetHeight ( double height )
{
Height = height ;
}
public double GetArea ( )
{
return Width * Height ;
}
}
class Square : Rectangle
{
public double SetWidth ( double width )
{
Width = Height = width ;
}
public double SetHeight ( double height )
{
Width = Height = height ;
}
}
Drawable RenderLargeRectangles ( Rectangle rectangles )
{
foreach ( rectangle in rectangles )
{
rectangle . SetWidth ( 4 ) ;
rectangle . SetHeight ( 5 ) ;
var area = rectangle . GetArea ( ) ; // BAD: Will return 25 for Square. Should be 20.
rectangle . Render ( area ) ;
}
}
var rectangles = new [ ] { new Rectangle ( ) , new Rectangle ( ) , new Square ( ) } ;
RenderLargeRectangles ( rectangles ) ;جيد:
abstract class ShapeBase
{
protected double Width = 0 ;
protected double Height = 0 ;
abstract public double GetArea ( ) ;
public Drawable Render ( double area )
{
// ...
}
}
class Rectangle : ShapeBase
{
public void SetWidth ( double width )
{
Width = width ;
}
public void SetHeight ( double height )
{
Height = height ;
}
public double GetArea ( )
{
return Width * Height ;
}
}
class Square : ShapeBase
{
private double Length = 0 ;
public double SetLength ( double length )
{
Length = length ;
}
public double GetArea ( )
{
return Math . Pow ( Length , 2 ) ;
}
}
Drawable RenderLargeRectangles ( Rectangle rectangles )
{
foreach ( rectangle in rectangles )
{
if ( rectangle is Square )
{
rectangle . SetLength ( 5 ) ;
}
else if ( rectangle is Rectangle )
{
rectangle . SetWidth ( 4 ) ;
rectangle . SetHeight ( 5 ) ;
}
var area = rectangle . GetArea ( ) ;
rectangle . Render ( area ) ;
}
}
var shapes = new [ ] { new Rectangle ( ) , new Rectangle ( ) , new Square ( ) } ;
RenderLargeRectangles ( shapes ) ;⬆ العودة إلى الأعلى
ينص ISP على أنه "لا ينبغي إجبار العملاء على الاعتماد على واجهات لا يستخدمونها".
مثال جيد للنظر إلى هذا يوضح أن هذا المبدأ هو الفئات التي تتطلب كائنات إعدادات كبيرة. عدم مطالبة العملاء بإعداد كميات هائلة من الخيارات مفيدة ، لأن معظم الوقت لن يحتاجوا إلى جميع الإعدادات. مما يجعلها اختياريًا يساعد في منع وجود "واجهة دهنية".
سيء:
public interface IEmployee
{
void Work ( ) ;
void Eat ( ) ;
}
public class Human : IEmployee
{
public void Work ( )
{
// ....working
}
public void Eat ( )
{
// ...... eating in lunch break
}
}
public class Robot : IEmployee
{
public void Work ( )
{
//.... working much more
}
public void Eat ( )
{
//.... robot can't eat, but it must implement this method
}
}جيد:
ليس كل عامل موظف ، لكن كل موظف عامل.
public interface IWorkable
{
void Work ( ) ;
}
public interface IFeedable
{
void Eat ( ) ;
}
public interface IEmployee : IFeedable , IWorkable
{
}
public class Human : IEmployee
{
public void Work ( )
{
// ....working
}
public void Eat ( )
{
//.... eating in lunch break
}
}
// robot can only work
public class Robot : IWorkable
{
public void Work ( )
{
// ....working
}
}⬆ العودة إلى الأعلى
ينص هذا المبدأ على شيئين أساسيين:
قد يكون من الصعب فهم ذلك في البداية ، ولكن إذا كنت قد عملت مع .NET/.NET Core Framework ، فقد رأيت تطبيقًا لهذا المبدأ في شكل حقن التبعية (DI). على الرغم من أنها ليست مفاهيم متطابقة ، إلا أن DIP تحافظ على وحدات عالية المستوى من معرفة تفاصيل وحداتها منخفضة المستوى وإعدادها. يمكن أن ينجز هذا من خلال DI. فائدة كبيرة من هذا هو أنه يقلل من الاقتران بين الوحدات. الاقتران هو نمط تطوير سيء للغاية لأنه يجعل الكود الخاص بك من الصعب إعادة تشكيله.
سيء:
public abstract class EmployeeBase
{
protected virtual void Work ( )
{
// ....working
}
}
public class Human : EmployeeBase
{
public override void Work ( )
{
//.... working much more
}
}
public class Robot : EmployeeBase
{
public override void Work ( )
{
//.... working much, much more
}
}
public class Manager
{
private readonly Robot _robot ;
private readonly Human _human ;
public Manager ( Robot robot , Human human )
{
_robot = robot ;
_human = human ;
}
public void Manage ( )
{
_robot . Work ( ) ;
_human . Work ( ) ;
}
}جيد:
public interface IEmployee
{
void Work ( ) ;
}
public class Human : IEmployee
{
public void Work ( )
{
// ....working
}
}
public class Robot : IEmployee
{
public void Work ( )
{
//.... working much more
}
}
public class Manager
{
private readonly IEnumerable < IEmployee > _employees ;
public Manager ( IEnumerable < IEmployee > employees )
{
_employees = employees ;
}
public void Manage ( )
{
foreach ( var employee in _employees )
{
_employee . Work ( ) ;
}
}
}⬆ العودة إلى الأعلى
حاول مراقبة المبدأ الجاف.
افعل أفضل ما لديك لتجنب الكود المكررة. يعد الكود المكرر سيئًا لأنه يعني أن هناك أكثر من مكان لتغيير شيء ما إذا كنت بحاجة إلى تغيير بعض المنطق.
تخيل لو قمت بتشغيل مطعم وتتابعت مخزونك: جميع الطماطم والبصل والثوم والتوابل ، وما إلى ذلك. إذا كان لديك قوائم متعددة تحتفظ بها ، فيجب تحديث جميعها عند تقديم طبق مع الطماطم فيها. إذا كان لديك قائمة واحدة فقط ، فهناك مكان واحد فقط للتحديث!
في كثير من الأحيان يكون لديك رمز مكرر لأن لديك شيئان مختلفان أو أكثر ، والتي تشترك في الكثير من القواسم المشتركة ، لكن اختلافاتها تجبرك على أن يكون لديك وظيفتين أو أكثر منفصلين تقومان بالكثير من الأشياء نفسها. تعني إزالة الكود المكررة إنشاء تجريد يمكنه التعامل مع هذه المجموعة من الأشياء المختلفة مع وظيفة/وحدة/فئة واحدة فقط.
إن الحصول على التجريد بشكل صحيح أمر بالغ الأهمية ، ولهذا السبب يجب عليك اتباع المبادئ الصلبة الموضوعة في قسم الفئات. يمكن أن تكون التجريدات السيئة أسوأ من الكود المكررة ، لذا كن حذرًا! بعد قولي هذا ، إذا كنت تستطيع إجراء تجريد جيد ، فافعل ذلك! لا تكرر نفسك ، وإلا ستجد نفسك تقوم بتحديث أماكن متعددة في أي وقت تريد تغيير شيء واحد.
سيء:
public List < EmployeeData > ShowDeveloperList ( Developers developers )
{
foreach ( var developers in developer )
{
var expectedSalary = developer . CalculateExpectedSalary ( ) ;
var experience = developer . GetExperience ( ) ;
var githubLink = developer . GetGithubLink ( ) ;
var data = new [ ] {
expectedSalary ,
experience ,
githubLink
} ;
Render ( data ) ;
}
}
public List < ManagerData > ShowManagerList ( Manager managers )
{
foreach ( var manager in managers )
{
var expectedSalary = manager . CalculateExpectedSalary ( ) ;
var experience = manager . GetExperience ( ) ;
var githubLink = manager . GetGithubLink ( ) ;
var data =
new [ ] {
expectedSalary ,
experience ,
githubLink
} ;
render ( data ) ;
}
}جيد:
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
var expectedSalary = employees . CalculateExpectedSalary ( ) ;
var experience = employees . GetExperience ( ) ;
var githubLink = employees . GetGithubLink ( ) ;
var data =
new [ ] {
expectedSalary ,
experience ,
githubLink
} ;
render ( data ) ;
}
}جيد جدًا:
من الأفضل استخدام نسخة مضغوطة من الرمز.
public List < EmployeeData > ShowList ( Employee employees )
{
foreach ( var employee in employees )
{
render ( new [ ] {
employee . CalculateExpectedSalary ( ) ,
employee . GetExperience ( ) ,
employee . GetGithubLink ( )
} ) ;
}
}⬆ العودة إلى الأعلى
الاختبار أكثر أهمية من الشحن. إذا لم يكن لديك اختبارات أو مبلغ غير كافٍ ، فكل مرة لا تشحن فيها رمزًا ، فلن تكون متأكدًا من أنك لم تكسر أي شيء. إن اتخاذ قرار بشأن ما يشكل مبلغًا كافيًا متروك لفريقك ، ولكن وجود تغطية بنسبة 100 ٪ (جميع البيانات والفروع) هو كيف تحقق ثقة عالية للغاية وراحة ذهنية. هذا يعني أنه بالإضافة إلى وجود إطار اختبار رائع ، تحتاج أيضًا إلى استخدام أداة تغطية جيدة.
ليس هناك عذر لعدم كتابة الاختبارات. هناك الكثير من أطر الاختبار الجيدة .NET ، لذلك ابحث عن فريق يفضله فريقك. عندما تجد واحدة تعمل لفريقك ، فإنها تهدف دائمًا إلى كتابة اختبارات لكل ميزة/وحدة جديدة تقدمها. إذا كانت الطريقة المفضلة لديك هي التطوير الذي يحركه (TDD) ، فهذا أمر رائع ، ولكن النقطة الرئيسية هي التأكد من وصولك إلى أهداف التغطية الخاصة بك قبل إطلاق أي ميزة ، أو إعادة إنشاء واحدة موجودة.
يضمن أن اختباراتك تركز على الليزر ولا تختبر الأشياء الموسيقية (غير المرتبطة) ، وهي قوى AAA الأب المستخدم لجعل رموزك أكثر نظافة وقابلة للقراءة.
سيء:
public class MakeDotNetGreatAgainTests
{
[ Fact ]
public void HandleDateBoundaries ( )
{
var date = new MyDateTime ( "1/1/2015" ) ;
date . AddDays ( 30 ) ;
Assert . Equal ( "1/31/2015" , date ) ;
date = new MyDateTime ( "2/1/2016" ) ;
date . AddDays ( 28 ) ;
Assert . Equal ( "02/29/2016" , date ) ;
date = new MyDateTime ( "2/1/2015" ) ;
date . AddDays ( 28 ) ;
Assert . Equal ( "03/01/2015" , date ) ;
}
}جيد:
public class MakeDotNetGreatAgainTests
{
[ Fact ]
public void Handle30DayMonths ( )
{
// Arrange
var date = new MyDateTime ( "1/1/2015" ) ;
// Act
date . AddDays ( 30 ) ;
// Assert
Assert . Equal ( "1/31/2015" , date ) ;
}
[ Fact ]
public void HandleLeapYear ( )
{
// Arrange
var date = new MyDateTime ( "2/1/2016" ) ;
// Act
date . AddDays ( 28 ) ;
// Assert
Assert . Equal ( "02/29/2016" , date ) ;
}
[ Fact ]
public void HandleNonLeapYear ( )
{
// Arrange
var date = new MyDateTime ( "2/1/2015" ) ;
// Act
date . AddDays ( 28 ) ;
// Assert
Assert . Equal ( "03/01/2015" , date ) ;
}
}soure https://www.codingblocks.net/podcast/how-to-write-amazing-unit-tests
⬆ العودة إلى الأعلى
ملخص إرشادات البرمجة غير المتزامنة
| اسم | وصف | استثناءات |
|---|---|---|
| تجنب الفراغ غير المتزامن | تفضل أساليب مهمة ASYNC على طرق الفراغ ASYNC | معالجات الأحداث |
| غير متزامن على طول الطريق | لا تقم بخلط كود الحظر والمتزامن | طريقة وحدة التحكم الرئيسية (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 ، وما إلى ذلك) ، لكن ليس من الجيد التقديم على المهام الحاسوبية (Traverse على القائمة الضخمة ، وتقديم صورة Hugge ، وما إلى ذلك). لأنه سيصدر مؤشر ترابط الحجز إلى تجمع الخيوط ولن يتضمن وحدة المعالجة المركزية/النوى المتاحة معالجة هذه المهام. لذلك ، يجب أن نتجنب استخدام Async/في انتظار المهام المرتبطة بالتناسب.
للتعامل مع المهام المرتبطة بالحساب ، تفضل LongRunning Task.Factory.CreateNew مع TaskCreationOptions سيبدأ مؤشر ترابط خلفية جديد لمعالجة مهمة حدود حسابية ثقيلة دون إطلاقها إلى تجمع مؤشرات الترابط حتى يتم الانتهاء من المهمة.
تعرف على أدواتك
هناك الكثير لنتعلمه عن Async وانتظر ، ومن الطبيعي أن تتعرض للارتباك قليلاً. إليك مرجع سريع للحلول للمشاكل الشائعة.
حلول للمشاكل المشتركة المشتركة
| مشكلة | حل |
|---|---|
| إنشاء مهمة لتنفيذ الرمز | Task.Run أو TaskFactory.StartNew (وليس مُنشئ Task أو Task.Start ) |
| قم بإنشاء غلاف مهمة لعملية أو حدث | TaskFactory.FromAsync أو TaskCompletionSource<T> |
| دعم الإلغاء | CancellationTokenSource و CancellationToken |
| الإبلاغ عن التقدم | IProgress<T> Progress<T> |
| التعامل مع تدفقات البيانات | TPL Dataflow أو الامتدادات التفاعلية |
| مزامنة الوصول إلى مورد مشترك | SemaphoreSlim |
| تهيئة مورد بشكل غير متزامن | AsyncLazy<T> |
| هياكل منتج/مستهلك جاهزة للمتزامنة | TPL Dataflow أو AsyncCollection<T> |
اقرأ مستند النمط غير المتزامن (TAP) المستند إلى المهام. إنه مكتوب بشكل جيد للغاية ، ويتضمن إرشادات حول تصميم واجهة برمجة التطبيقات والاستخدام السليم لـ Async/Await (بما في ذلك الإلغاء والإبلاغ عن التقدم المحرز).
هناك العديد من التقنيات الجديدة الصديقة للانتظار التي يجب استخدامها بدلاً من تقنيات الحظر القديمة. إذا كان لديك أي من هذه الأمثلة القديمة في رمز Async الجديد ، فأنت تفعل ذلك بشكل خاطئ (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 . إنها ممارسة سيئة لرمي استثناء تم صيده باستخدام 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 بحيث لا يزال هذا التعليق عديم الفائدة. ومع ذلك ، سيكون من الصعب التعبير عن طريق الكود لماذا اختار المطور خوارزمية تجزئة DJB2 بدلاً من SHA-1 أو وظيفة تجزئة أخرى. في هذه الحالة ، يكون التعليق مقبولًا.
جيد:
public int Hash ( string data )
{
var hash = 0 ;
var length = data . length ;
for ( var i = 0 ; i < length ; i ++ )
{
var character = data [ i ] ;
// use of djb2 hash algorithm as it has a good compromise
// between speed and low collision with a very simple implementation
hash = ( ( hash << 5 ) - hash ) + character ;
hash = ConvertTo32BitInt ( hash ) ;
}
return hash ;
}
private int ConvertTo32BitInt ( int value )
{
return value & value ;
}⬆ العودة إلى الأعلى
شكرًا لجميع الأشخاص الذين ساهموا بالفعل في مشروع clean-code-dotnet
أحب عملنا وساعدنا في مواصلة أنشطتنا؟ [كن مؤيدًا]
كن راعياً واحصل على شعارك في ReadMe على Github مع رابط لموقعك. [كن راعياً]
إلى الحد الممكن بموجب القانون ، تنازل Thangchung من جميع حقوق الطبع والنشر والحقوق ذات الصلة أو المجاورة لهذا العمل.