تتناول هذه المقالة الأخطاء في مشروع مفتوح المصدر تم العثور عليه باستخدام محلل ثابت. إليك بعض الأشياء البسيطة التي يمكن أن تساعدك على تجنبها. على سبيل المثال ، استخدام التركيبات النحوية للغة منذ C # 8.0. اتمنى ان تكون ممتعة قراءة سعيدة.
QuantConnect Lean هو محرك تداول خوارزمي مفتوح المصدر تم تصميمه من أجل البحث الإستراتيجي السهل والاختبار العكسي والتداول المباشر. متوافق مع أنظمة التشغيل Windows و Linux و macOS. يتكامل مع مزودي البيانات الرئيسيين وشركات الوساطة لنشر استراتيجيات التداول الخوارزمية بسرعة.
تم إجراء الفحص باستخدام محلل ثابت PVS-Studio . PVS-Studio هي أداة لتحديد الأخطاء ونقاط الضعف المحتملة في التعليمات البرمجية المصدر للبرامج المكتوبة بلغة C و C ++ و C # و Java على أنظمة التشغيل Windows و Linux و macOS
الحوادث ليست عرضية
public virtual DateTime NextDate(....)
{
....
// both are valid dates, so chose one randomly
if ( IsWithinRange(nextDayOfWeek, minDateTime, maxDateTime)
&& IsWithinRange(previousDayOfWeek, minDateTime, maxDateTime))
{
return _random.Next(0, 1) == 0 // <=
? previousDayOfWeek
: nextDayOfWeek;
}
....
}
التعبير V3022 '_random.Next (0، 1) == 0' صحيح دائمًا. RandomValueGenerator.cs 142 النقطة
هي أنه يتم تحديد قيمة أو أخرى باحتمال 50٪. ومع ذلك ، في هذه الحالة ، سيعود الأسلوب التالي دائمًا 0.
هذا لأن النطاق لا يتضمن الوسيطة الثانية. أي أن القيمة التي يمكن للطريقة إرجاعها ستكون في النطاق [0،1). دعنا نصلح هذا:
public virtual DateTime NextDate(....)
{
....
// both are valid dates, so chose one randomly
if ( IsWithinRange(nextDayOfWeek, minDateTime, maxDateTime)
&& IsWithinRange(previousDayOfWeek, minDateTime, maxDateTime))
{
return _random.Next(0, 2) == 0
? previousDayOfWeek
: nextDayOfWeek;
}
....
}
تمرير معلمات النوع المرجعي
مثال
/// <summary>
/// Copy contents of the portfolio collection to a new destination.
/// </summary>
/// <remarks>
/// IDictionary implementation calling the underlying Securities collection
/// </remarks>
/// <param name="array">Destination array</param>
/// <param name="index">Position in array to start copying</param>
public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] array, int index)
{
array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
var i = 0;
foreach (var asset in Securities)
{
if (i >= index)
{
array[i] = new KeyValuePair<Symbol,SecurityHolding>(asset.Key,
asset.Value.Holdings);
}
i++;
}
}
يتم دائمًا إعادة كتابة "مصفوفة" V3061 في نص الأسلوب قبل استخدامها. SecurityPortfolioManager.cs 192
تأخذ الطريقة مجموعة وتقوم على الفور بالكتابة فوق قيمتها. توافق على أن هذا يبدو مريبًا إلى حد ما. لذلك دعونا نحاول فهم ما يجب أن تفعله هذه الطريقة.
من الشرح واسم العملية ، يتضح أنه يجب نسخ مصفوفة أخرى إلى المصفوفة التي تم تمريرها. ومع ذلك ، لن يحدث هذا ، وستبقى قيمة المصفوفة خارج الطريقة الحالية دون تغيير.
يحدث هذا لأن وسيطة الصفيفإلى الطريقة بالقيمة وليس بالإشارة. وبالتالي ، بعد إجراء عملية الإسناد ، سيتم تخزين المرجع إلى الكائن الجديد بواسطة متغير المصفوفة ، المتاح داخل الطريقة. ستبقى قيمة الوسيطة التي تم تمريرها إلى الطريقة دون تغيير. لإصلاح ذلك ، تحتاج إلى تمرير وسيطة نوع المرجع حسب المرجع:
public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] out array, // <=
int index)
{
array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
....
}
وبما أننا بالتأكيد كتابة مجموعة جديدة في الأسلوب، ونحن نستخدم من معدل بدلا من المرجع . هذا يعني على الفور أنه سيتم تعيين قيمة داخل المتغير.
بالمناسبة ، تُجدد هذه الحالة المجموعة التي يجمعها زميلي أندريه كاربوف والتي يمكنك التعلم عنها من مقالة " بدء تجميع الأخطاء في وظائف النسخ ".
تحرير الموارد
public static string ToSHA256(this string data)
{
var crypt = new SHA256Managed();
var hash = new StringBuilder();
var crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(data),
0,
Encoding.UTF8.GetByteCount(data));
foreach (var theByte in crypto)
{
hash.Append(theByte.ToStringInvariant("x2"));
}
return hash.ToString();
}
V3114 لا يتم التخلص من العنصر القابل للتصرف "تشفير " قبل إرجاع الطريقة. Extensions.cs 510
لفهم معنى هذا التشخيص ، دعنا أولاً نتذكر النظرية قليلاً. بعد إذنك ، سأأخذ معلومات من وثائق هذا التشخيص:
"يقوم جامع القمامة تلقائيًا بتحرير الذاكرة المرتبطة بالكائن المتحكم فيه إذا لم يعد مستخدمًا ولا توجد مراجع مرئية له. ومع ذلك ، من المستحيل التنبؤ بموعد حدوث المجموعة بالضبط. مجموعة البيانات المهملة (ما لم تقم باستدعائها يدويًا). كما أن أداة تجميع البيانات المهملة ليس لديها معرفة بالموارد غير المُدارة مثل المقابض أو النوافذ أو الملفات والتدفق المفتوحة. يتم استخدام التخلص عادةً لتحرير مثل هذه الموارد غير المُدارة. "
وهذا هو، قمنا بإنشاء سرداب متغير من نوع SHA256Managed ، الذي ينفذ IDisposable الواجهة . نتيجة لذلك ، عند الخروج من الطريقة ، لن يتم تحرير الموارد التي يحتمل الحصول عليها.
لمنع هذا ، أوصي باستخدام . و تخلص سيتم استدعاء أسلوب تلقائيا عندما متعرج إغلاق المرتبطة باستخدام يتم التوصل إلى بيان . تبدو هكذا:
public static string ToSHA256(this string data)
{
using (var crypt = new SHA256Managed())
{
var hash = new StringBuilder();
....
}
}
وإذا كنت لا تحب الأقواس المتعرجة ، فيمكنك الكتابة في C # 8.0 على النحو التالي:
public static string ToSHA256(this string data)
{
using var crypt = new SHA256Managed();
var hash = new StringBuilder();
....
}
يتمثل الاختلاف عن الخيار السابق في أنه يتم استدعاء طريقة التخلص عند الوصول إلى قوس الإغلاق المتعرج للطريقة . هذه نهاية المنطقة التي تم فيها إعلان القبو .
أرقام حقيقية
public bool ShouldPlot
{
get
{
....
if (Time.TimeOfDay.Hours < 10.25) return true;
....
}
}
public struct TimeSpan : IComparable,
IComparable<TimeSpan>,
IEquatable<TimeSpan>,
IFormattable
{
....
public double TotalHours { get; }
public int Hours { get; }
....
}
V3040 تتم مقارنة "10 .25 "الحرفية من النوع" مزدوج "بقيمة من النوع" int ". OpeningBreakoutAlgorithm.cs 426
يبدو من الغريب أنه في الحالة يتم مقارنة قيمة متغير من النوع int مع قيمة حرفية من النوع double . يبدو غريباً وبعض المتغيرات الأخرى تسأل عن نفسها بوضوح. وبالفعل ، إذا نظرنا إلى الحقول التي تحمل اسمًا مشابهًا لـ TimeOfDay ، فسنجد:
public double TotalHours { get; }
على الأرجح يجب أن يبدو الرمز كما يلي:
public bool ShouldPlot
{
get
{
....
if (Time.TimeOfDay.TotalHours < 10.25) return true;
....
}
}
تذكر أيضًا أنه لا يمكنك المقارنة من أجل المساواة المباشرة ("=="، "! =") أرقام الفاصلة العائمة. ولا تنسى اختيار النوع .
بيان التبديل
نصيحة 1
public IEnumerable<TradingDay> GetDaysByType(TradingDayType type,
DateTime start,
DateTime end)
{
Func<TradingDay, bool> typeFilter = day =>
{
switch (type) // <=
{
case TradingDayType.BusinessDay:
return day.BusinessDay;
case TradingDayType.PublicHoliday:
return day.PublicHoliday;
case TradingDayType.Weekend:
return day.Weekend;
case TradingDayType.OptionExpiration:
return day.OptionExpirations.Any();
case TradingDayType.FutureExpiration:
return day.FutureExpirations.Any();
case TradingDayType.FutureRoll:
return day.FutureRolls.Any();
case TradingDayType.SymbolDelisting:
return day.SymbolDelistings.Any();
case TradingDayType.EquityDividends:
return day.EquityDividends.Any();
};
return false;
};
return GetTradingDays(start, end).Where(typeFilter);
}
V3002 لا تغطي عبارة switch كافة قيم "TradingDayType" enum: EconomicEvent. TradingCalendar.cs 79
نوع المتغير نوع غير TradingDayType ، وهذا هو التعداد :
public enum TradingDayType
{
BusinessDay,
PublicHoliday,
Weekend,
OptionExpiration,
FutureExpiration,
FutureRoll,
SymbolDelisting,
EquityDividends,
EconomicEvent
}
إذا قمت بالعد ، سترى أن هناك 9 عناصر في التعداد ، ويتم تحليل 8 عناصر فقط في التبديل.قد ينشأ هذا الموقف بسبب توسيع الكود. لمنع هذا ، أوصي دائمًا باستخدام الإعداد الافتراضي صراحة :
public IEnumerable<TradingDay> GetDaysByType(TradingDayType type,
DateTime start,
DateTime end)
{
Func<TradingDay, bool> typeFilter = day =>
{
switch (type)
{
....
default:
return false;
};
};
return GetTradingDays(start, end).Where(typeFilter);
}
كما لاحظت ، انتقل بيان الإرجاع بعد التبديل إلى القسم الافتراضي . في هذه الحالة ، لم يتغير منطق البرنامج ، لكني ما زلت أنصحك بالكتابة بهذه الطريقة.
والسبب في ذلك هو قابلية امتداد الكود. في حالة الأصلي، يمكنك إضافة بأمان بعض المنطق قبل كاذبة عودة ، وليس الشك أن هذا هو الافتراضي "من ل بيان التبديل . الآن كل شيء واضح وواضح.
ومع ذلك ، إذا كنت تعتقد أنه في حالتك يجب دائمًا معالجة جزء فقط من عناصر التعداد ، فيمكنك حينئذٍ طرح استثناء:
default:
throw new CustomExeption("Invalid enumeration element");
أنا شخصياً تعلقت بهذا السكر النحوي C # 8.0:
Func<TradingDay, bool> typeFilter = day =>
{
return type switch
{
TradingDayType.BusinessDay => day.BusinessDay,
TradingDayType.PublicHoliday => day.PublicHoliday,
TradingDayType.Weekend => day.Weekend,
TradingDayType.OptionExpiration => day.OptionExpirations.Any(),
TradingDayType.FutureExpiration => day.FutureExpirations.Any(),
TradingDayType.FutureRoll => day.FutureRolls.Any(),
TradingDayType.SymbolDelisting => day.SymbolDelistings.Any(),
TradingDayType.EquityDividends => day.EquityDividends.Any(),
_ => false
};
};
نصيحة 2
public string[] GetPropertiesBy(SecuritySeedData type)
{
switch (type)
{
case SecuritySeedData.None:
return new string[0];
case SecuritySeedData.OpenInterest:
return new[] { "OpenInterest" }; // <=
case SecuritySeedData.OpenInterestTick:
return new[] { "OpenInterest" }; // <=
case SecuritySeedData.TradeTick:
return new[] {"Price", "Volume"};
....
case SecuritySeedData.Fundamentals:
return new string[0];
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
V3139 يقوم فرعي حالة أو أكثر بتنفيذ نفس التصرفات. SecurityCacheTests.cs 510
حالتان مختلفتان ترجعان نفس القيمة. في هذا الشكل ، يبدو مريبًا للغاية. على الفور هناك شعور بأنك نسخت ولصقت ونسيت التغيير. لذلك ، أوصي بأنه إذا كان يجب تنفيذ نفس المنطق لقيم مختلفة ، فقم بدمج الحالة على النحو التالي :
public string[] GetPropertiesBy(SecuritySeedData type)
{
switch (type)
{
case SecuritySeedData.None:
return new string[0];
case SecuritySeedData.OpenInterest:
case SecuritySeedData.OpenInterestTick:
return new[] { "OpenInterest" };
....
}
}
يشير هذا بوضوح إلى ما نريده ، بالإضافة إلى أنه يزيل السطر الإضافي من النص. :)
إذا البيان
مثال 1
[TestCaseSource(nameof(DataTypeTestCases))]
public void HandlesAllTypes<T>(....) where T : BaseData, new()
{
....
if ( symbol.SecurityType != SecurityType.Equity
|| resolution != Resolution.Daily
|| resolution != Resolution.Hour)
{
actualPricePointsEnqueued++;
dataPoints.Add(dataPoint);
}
....
}
V3022 Expression 'code.SecurityType! = SecurityType.Equity || القرار! = الدقة. يوميا || القرار! = الدقة.ساعة دائما صحيح. LiveTradingDataFeedTests.cs 1431
هذا الشرط صحيح دائمًا. بعد كل شيء، لحالة عدم الوفاء، و قرار متغير يجب أن يكون قيمة Resolution.Daily و Resolution.Hour في وقت واحد. النسخة المصححة الممكنة:
[TestCaseSource(nameof(DataTypeTestCases))]
public void HandlesAllTypes<T>(....) where T : BaseData, new()
{
....
if ( symbol.SecurityType != SecurityType.Equity
|| ( resolution != Resolution.Daily
&& resolution != Resolution.Hour))
{
actualPricePointsEnqueued++;
dataPoints.Add(dataPoint);
}
....
}
بعض المبادئ التوجيهية لبيان if . عندما يكون هناك شرط يتكون بالكامل من عوامل التشغيل "||" ، ثم بعد الكتابة ، تحقق مما إذا كان المتغير نفسه قد تم التحقق منه لعدم المساواة لشيء ما عدة مرات على التوالي.
الوضع مشابه مع عامل التشغيل "&&". إذا تم التحقق من متغير عدة مرات من أجل مساواة شيء ما ، فمن المرجح أن يكون هذا خطأ منطقيًا.
أيضًا ، إذا كتبت شرطًا مركبًا ، وكان يحتوي على "&&" و "||" ، فلا تتردد في وضع الأقواس. يمكن أن يساعدك ذلك في رؤية الخطأ أو تجنبه.
مثال 2
public static string SafeSubstring(this string value,
int startIndex,
int length)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
if (startIndex > value.Length - 1)
{
return string.Empty;
}
if (startIndex < -1)
{
startIndex = 0;
}
return value.Substring(startIndex,
Math.Min(length, value.Length - startIndex));
}
V3057 يمكن أن تتلقى الدالة "سلسلة فرعية" القيمة "-1" بينما يُتوقع وجود قيمة غير سالبة. افحص الحجة الأولى. StringExtensions.cs 311
يقول المحلل أنه يمكن تمرير القيمة -1 إلى الوسيطة الأولى لطريقة Substring . سيؤدي هذا إلى طرح استثناء من النوع System.ArgumentOutOfRangeException . دعونا نرى لماذا يمكن أن تظهر هذه القيمة. في هذا المثال ، لسنا مهتمين بالشرطين الأولين ، لذلك سيتم حذفهما في التفكير.
المعلمة startIndex من النوع intلذلك ، تكون قيمها في النطاق [-2147483648 ، 2147483647]. لذلك ، لمنع تجاوز حدود المصفوفة ، كتب المطور الشرط التالي:
if (startIndex < -1)
{
startIndex = 0;
}
أي أنه كان من المفترض أنه إذا ظهرت قيمة سالبة ، فإننا ببساطة نغيرها إلى 0. ولكن بدلاً من "<=" كتبنا "<" ، والآن الحد الأدنى لنطاق متغير startIndex (من وجهة نظر المحلل) هو -1.
أقترح استخدام بناء مثل هذا في مواقف مثل هذا:
if (variable < value)
{
variable = value;
}
هذه المجموعة أسهل في القراءة ، نظرًا لوجود قيمة أقل. لذلك ، أقترح إصلاح المشكلة مثل هذا:
public static string SafeSubstring(....)
{
....
if (startIndex < 0)
{
startIndex = 0;
}
return value.Substring(startIndex,
Math.Min(length, value.Length - startIndex));
}
يمكنك القول أنه في المثال الأولي يمكننا ببساطة تغيير العلامة في الحالة:
if (startIndex <= -1)
{
startIndex = 0;
}
الخطأ يختفي أيضا. ومع ذلك ، سيبدو المنطق كما يلي:
if (variable <= value - 1)
{
variable = value;
}
توافق على أنها تبدو غارقة.
مثال 3
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
var futureMarginModel = buyingPowerModel as FutureMarginModel;
if (buyingPowerModel == null)
{
throw new Exception($"Invalid buying power model. " +
$"Found: {buyingPowerModel.GetType().Name}. " +
$"Expected: {nameof(FutureMarginModel)}");
}
....
}
V3080 احتمال عدم وجود مرجع فارغ. ضع في اعتبارك فحص "PurchasePowerModel". BasicTemplateFuturesAlgorithm.cs 107
V3019 من المحتمل مقارنة متغير غير صحيح مع قيمة خالية بعد تحويل النوع باستخدام كلمة رئيسية "as". تحقق من المتغيرات "PurchasePowerModel" ، و "futureMarginModel". BasicTemplateFuturesAlgorithm.cs 105
قطعة غريبة للغاية. يُصدر المحلل تحذيرين في وقت واحد. وهي في الحقيقة تحتوي على المشكلة وسببها. أولاً ، دعنا نرى ما سيحدث إذا تم استيفاء الشرط. نظرًا لأن buyPowerModel في الداخل سيكون فارغًا تمامًا ، فسيحدث إلغاء المرجع :
$"Found: {buyingPowerModel.GetType().Name}. "
والسبب هو أن المتغير يختلط في الشرط ، والذي يتم مقارنته بالصفر . بدلاً من buyPowerModel ، يجب كتابة FutureMarginModel بشكل صريح . نسخة مصححة:
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
var futureMarginModel = buyingPowerModel as FutureMarginModel;
if (futureMarginModel == null)
{
throw new Exception($"Invalid buying power model. " +
$"Found: {buyingPowerModel.GetType().Name}. " +
$"Expected: {nameof(FutureMarginModel)}");
}
....
}
ومع ذلك ، لا تزال هناك مشكلة في إلغاء مرجعية شراء PowerModel داخل الشرط. لأن FutureMarginModel سيكون فارغًا ليس فقط عندما لا يكون FutureMarginModel ، ولكن أيضًا عند الشراء يكون PowerModel فارغًا . لذلك أقترح هذا الخيار:
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
var futureMarginModel = buyingPowerModel as FutureMarginModel;
if (futureMarginModel == null)
{
string foundType = buyingPowerModel?.GetType().Name
?? "the type was not found because the variable is null";
throw new Exception($"Invalid buying power model. " +
$"Found: {foundType}. " +
$"Expected: {nameof(FutureMarginModel)}");
}
....
}
أنا شخصياً أصبحت أحب كتابة مثل هذه التركيبات باستخدام is . هذا يجعل الأمر أقل رمزًا ويصعب ارتكاب الخطأ. هذا المثال مشابه تمامًا للمثال أعلاه:
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (!(buyingPowerModel is FutureMarginModel futureMarginModel))
{
....
}
....
}
علاوة على ذلك ، في C # 9.0 سنضيف القدرة على كتابة الكلمة الرئيسية وليس :
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (buyingPowerModel is not FutureMarginModel futureMarginModel)
{
....
}
....
}
مثال 4
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
V3004 عبارة "then" تعادل جملة "else". FuturesExpiryFunctions.cs 1561 في
ظل ظروف مختلفة ، يتم تنفيذ نفس المنطق. نظرًا لأن إحدى الوسيطات عبارة عن حرف رقمي ، فقد يلزم تمرير قيمة مختلفة. فمثلا:
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 2);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
لكن هذا ليس أكثر من افتراض. هنا أود أن ألفت انتباهكم إلى حقيقة حدوث خطأ في تهيئة الحاوية. حجم هذه التهيئة ما يقرب من 2000 سطر:
أيضًا ، تتشابه أجزاء الكود الداخلي مع بعضها البعض ، وهو أمر منطقي ، لأن المجموعة يتم ملؤها هنا ببساطة. لذلك ، كن حذرًا بشكل خاص عند نسخ شيء ما في مناطق كبيرة ومتشابهة. قم بإجراء التغييرات على الفور ، لأنه بعد ذلك سوف تتعب عيناك ولن ترى المشكلة.
مثال 5
public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
....
if (request.Method == Method.GET && request.Parameters.Count > 0)
{
var parameters = request.Parameters.Count > 0
? string.Join(....)
: string.Empty;
url = $"{request.Resource}?{parameters}";
}
}
طلب التعبير V3022 '. المعلمات.العدد > 0' صحيح دائمًا. GDAXBrokerage.Utility.cs 63
الشرط في المعامل الثلاثي يكون دائمًا صحيحًا لأن هذا الفحص قد تم إجراؤه بالفعل أعلاه. الآن هذا إما فحص زائد ، أو تم خلط عوامل التشغيل "&&" و "||" في الحالة أعلاه.
لتجنب ذلك ، عندما تكون في حالة ما ، ضع في اعتبارك دائمًا القيم التي ستدخلها.
النسخة المصححة الممكنة:
public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
....
if (request.Method == Method.GET && request.Parameters.Count > 0)
{
var parameters = string.Join(....);
url = $"{request.Resource}?{parameters}";
}
}
مثال 6
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan == UserPlan.Free)
{
MaxOrders = 10000;
}
else
{
MaxOrders = int.MaxValue;
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders; // <=
....
}
V3008 يتم تعيين قيم لمتغير "MaxOrders" مرتين على التوالي. ربما هذا خطأ. فحص الأسطر: 244 ، 240. BacktestingSetupHandler.cs 244
هنا ، يتم تعيين قيمة لمتغير MaxOrders مرتين على التوالي. وهذا يعني أن المنطق بشروط زائدة عن الحاجة.
لإصلاح هذا ، لدينا خياران. إما أن نزيل التخصيصات في فروع then-else ، أو التخصيص بعد الشرط. على الأرجح ، تمت تغطية الرمز عن طريق الاختبارات ، ويعمل البرنامج بشكل صحيح. لذلك ، سنترك المهمة الأخيرة فقط. النسخة المصححة الممكنة:
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan != UserPlan.Free)
{
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders;
....
}
الأخطاء الشائعة للبشر
سيتم النظر هنا في أخطاء مثل النسخ واللصق والمفاتيح التي يتم ضغطها عن طريق الخطأ وما إلى ذلك بشكل عام ، أكثر مشاكل النقص البشري شيوعًا. نحن لسنا آلات ، لذلك مثل هذه المواقف طبيعية.
توصيات عامة لهم:
- إذا نسخت شيئًا ما ، فقم بإجراء تغييرات على النسخة بمجرد لصقه ؛
- إجراء مراجعة مدونة ؛
- استخدم أدوات خاصة للبحث عن الأخطاء من أجلك.
الموقف 1
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMax.IsReady && _medianMax.IsReady;
}
V3001 هناك تعبيرات فرعية متطابقة '_medianMax.IsReady' على يسار ويمين عامل التشغيل "&&". FisherTransform.cs 72
في هذا المثال ، يجب أن يعتمد حقل IsReady على شرطين ، ولكن في الواقع يعتمد على أحدهما. كل ذلك بسبب خطأ مطبعي. على الأرجح، أنها كتبت _medianMax بدلا من _medianMin . نسخة مصححة:
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMin.IsReady && _medianMax.IsReady;
}
الموقف 2
public BacktestResultPacket(....) : base(PacketType.BacktestResult)
{
try
{
Progress = Math.Round(progress, 3);
SessionId = job.SessionId; // <=
PeriodFinish = endDate;
PeriodStart = startDate;
CompileId = job.CompileId;
Channel = job.Channel;
BacktestId = job.BacktestId;
Results = results;
Name = job.Name;
UserId = job.UserId;
ProjectId = job.ProjectId;
SessionId = job.SessionId; // <=
TradeableDates = job.TradeableDates;
}
catch (Exception err)
{
Log.Error(err);
}
}
V3008 يتم تعيين قيم لمتغير "SessionId" مرتين على التوالي. ربما هذا خطأ. فحص الأسطر: 182 ، 172. BacktestResultPacket.cs 182
يحتوي الفصل على العديد من الحقول التي يجب تهيئتها - العديد من الأسطر في المنشئ. يتم دمج كل شيء ويتم تهيئة حقل واحد عدة مرات. في هذه الحالة ، قد يكون هناك تهيئة غير ضرورية أو نسوا تهيئة بعض الحقول الأخرى.
إذا كنت مهتمًا ، يمكنك إلقاء نظرة على الأخطاء الأخرى التي تم العثور عليها بواسطة قاعدة التشخيص هذه.
الموقف 3
private const string jsonWithScore =
"{" +
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
"\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
"\"source-model\":\"mySourceModel-1\"," +
"\"generated-time\":1520711961.00055," +
"\"created-time\":1520711961.00055," +
"\"close-time\":1520711961.00055," +
"\"symbol\":\"BTCUSD XJ\"," +
"\"ticker\":\"BTCUSD\"," +
"\"type\":\"price\"," +
"\"reference\":9143.53," +
"\"reference-final\":9243.53," +
"\"direction\":\"up\"," +
"\"period\":5.0," +
"\"magnitude\":0.025," +
"\"confidence\":null," +
"\"weight\":null," +
"\"score-final\":true," +
"\"score-magnitude\":1.0," +
"\"score-direction\":1.0," +
"\"estimated-value\":1113.2484}";
private const string jsonWithExpectedOutputFromMissingCreatedTimeValue =
"{" +
"\"id\":\"e02be50f56a8496b9ba995d19a904ada\"," +
"\"group-id\":\"a02be50f56a8496b9ba995d19a904ada\"," +
"\"source-model\":\"mySourceModel-1\"," +
"\"generated-time\":1520711961.00055," +
"\"created-time\":1520711961.00055," +
"\"close-time\":1520711961.00055," +
"\"symbol\":\"BTCUSD XJ\"," +
"\"ticker\":\"BTCUSD\"," +
"\"type\":\"price\"," +
"\"reference\":9143.53," +
"\"reference-final\":9243.53," +
"\"direction\":\"up\"," +
"\"period\":5.0," +
"\"magnitude\":0.025," +
"\"confidence\":null," +
"\"weight\":null," +
"\"score-final\":true," +
"\"score-magnitude\":1.0," +
"\"score-direction\":1.0," +
"\"estimated-value\":1113.2484}";
V3091 تحليل تجريبي. من الممكن أن يكون هناك خطأ مطبعي داخل السلسلة الحرفية. كلمة "النتيجة" مشبوهة. InsightJsonConverterTests.cs 209
آسف على الشفرة الكبيرة والمخيفة . هنا الحقول المختلفة لها نفس القيم. هذا خطأ كلاسيكي من عائلة النسخ واللصق. منسوخة ومدروسة ونسيت إجراء تغييرات - هذا هو الخطأ.
الموقف 4
private void ScanForEntrance()
{
var shares = (int)(allowedDollarLoss/expectedCaptureRange);
....
if (ShouldEnterLong)
{
MarketTicket = MarketOrder(symbol, shares);
....
}
else if (ShouldEnterShort)
{
MarketTicket = MarketOrder(symbol, - -shares); // <=
....
StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
....
}
....
}
V3075 يتم تنفيذ العملية "-" مرتين أو أكثر على التوالي. ضع في اعتبارك فحص تعبير "- -shares". OpeningBreakoutAlgorithm.cs 328 تم
تطبيق عامل التشغيل الأحادي "-" مرتين على التوالي. وبالتالي ، فإن القيمة التي تم تمريرها إلى طريقة MarketOrder ستظل دون تغيير. من الصعب جدًا تحديد عدد السلبيات الأحادية التي يجب تركها هنا. ربما يكون عامل تقليل البادئة "-" مطلوبًا هنا بشكل عام ، ولكن تم ضرب مفتاح المسافة عن طريق الخطأ . الخيارات مظلمة ، لذا فإن أحد الخيارات الممكنة التي تم تصحيحها:
private void ScanForEntrance()
{
....
if (ShouldEnterLong)
{
MarketTicket = MarketOrder(symbol, shares);
....
}
else if (ShouldEnterShort)
{
MarketTicket = MarketOrder(symbol, -shares);
....
StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
....
}
....
}
الموقف 5
private readonly SubscriptionDataConfig _config;
private readonly DateTime _date;
private readonly bool _isLiveMode;
private readonly BaseData _factory;
public ZipEntryNameSubscriptionDataSourceReader(
SubscriptionDataConfig config,
DateTime date,
bool isLiveMode)
{
_config = config;
_date = date;
_isLiveMode = isLiveMode;
_factory = _factory = config.GetBaseDataInstance(); // <=
}
V3005 يتم تخصيص المتغير "_factory" لنفسه. ZipEntryNameSubscriptionDataSourceReader.cs 50
Paul _factory مرتين يتم تعيين نفس القيمة لها. لا يوجد سوى أربعة حقول في الفصل ، لذلك من المحتمل أن يكون هذا مجرد خطأ مطبعي. نسخة مصححة:
public ZipEntryNameSubscriptionDataSourceReader(....)
{
_config = config;
_date = date;
_isLiveMode = isLiveMode;
_factory = config.GetBaseDataInstance();
}
استنتاج
هناك العديد من الأماكن حيث يمكنك ارتكاب الأخطاء. نلاحظ البعض ونصححها على الفور. تم تصحيح جزء منه لمراجعة التعليمات البرمجية ، وأوصي بتعيين جزء منه لأدوات خاصة.
أيضًا ، إذا أعجبك هذا التنسيق ، فيرجى الكتابة عنه. سأفعل شيئًا مشابهًا آخر. شكرا لك!
إذا كنت ترغب في مشاركة هذه المقالة مع جمهور يتحدث الإنجليزية ، فيرجى استخدام رابط الترجمة: نيكولاي ميرونوف. التحدث عن الأخطاء في كود QuantConnect Lean Code .