Dieser Artikel beschreibt Fehler in einem Open Source-Projekt, die mit dem statischen Analysator gefunden wurden. Hier sind einige einfache Dinge, die Ihnen helfen können, sie zu vermeiden. Verwenden Sie beispielsweise syntaktische Sprachkonstrukte seit C # 8.0. Hoffe es wird interessant. Fröhliches Lesen.
QuantConnect Lean ist eine Open-Source-Engine für den algorithmischen Handel, die für einfache Strategieforschung, Backtesting und Live-Handel entwickelt wurde. Kompatibel mit Windows, Linux und MacOS. Integriert sich in gängige Datenanbieter und Maklerunternehmen, um schnell algorithmische Handelsstrategien einzusetzen.
Die Prüfung wurde mit dem statischen Analysegerät PVS-Studio durchgeführt . PVS-Studio ist ein Tool zum Erkennen von Fehlern und potenziellen Schwachstellen im Quellcode von Programmen, die in C, C ++, C # und Java unter Windows, Linux und macOS geschrieben wurden.
Unfälle sind nicht zufällig
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 Ausdruck '_random.Next (0, 1) == 0' ist immer wahr. RandomValueGenerator.cs 142
Der Punkt war, dass entweder der eine oder andere Wert mit einer Wahrscheinlichkeit von 50% ausgewählt wird. In diesem Fall gibt die Next- Methode jedoch immer 0 zurück.
Dies liegt daran, dass der Bereich das zweite Argument nicht enthält. Das heißt, der Wert, den die Methode zurückgeben kann, liegt im Bereich [0,1]. Lassen Sie uns das beheben:
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;
}
....
}
Übergeben von Referenztypparametern
Beispiel
/// <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 Der Parameter 'array' wird vor seiner Verwendung immer im Methodenkörper neu geschrieben. SecurityPortfolioManager.cs 192
Die Methode nimmt eine Sammlung und überschreibt sofort ihren Wert. Stimmen Sie zu, dass dies ziemlich verdächtig aussieht. Versuchen wir also zu verstehen, was diese Methode tun sollte.
Aus dem Kommentar und dem Namen der Methode wird deutlich, dass ein anderes Array in das übergebene Array kopiert werden sollte. Dies ist jedoch nicht der Fall, und der Wert des Arrays außerhalb der aktuellen Methode bleibt unverändert.
Dies geschieht aufgrund des Array- Arguments wird als Wert an die Methode übergeben, nicht als Referenz. Nach dem Ausführen der Zuweisungsoperation wird der Verweis auf das neue Objekt von der Array- Variablen gespeichert , die innerhalb der Methode verfügbar ist. Der Wert des an die Methode übergebenen Arguments bleibt unverändert. Um dies zu beheben, müssen Sie das Argument des Referenztyps als Referenz übergeben:
public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] out array, // <=
int index)
{
array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
....
}
Da wir sicherlich ein neues Array in die Methode schreiben, verwenden wir den Modifikator out anstelle von ref . Dies bedeutet sofort, dass der Variablen ein innerer Wert zugewiesen wird.
Übrigens füllt dieser Fall die Sammlung auf, die mein Kollege Andrey Karpov sammelt und über die Sie aus dem Artikel " Beginn des Sammelns von Fehlern in Kopierfunktionen " lernen können .
Ressourcen freisetzen
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 IDisposable-Objekt 'crypt' wird nicht entsorgt, bevor die Methode zurückgegeben wird. Extensions.cs 510
Um die Bedeutung dieser Diagnose zu verstehen, erinnern wir uns zunächst ein wenig an die Theorie. Mit Ihrer Erlaubnis werde ich Informationen aus der Dokumentation für diese Diagnose entnehmen:
"Der Garbage Collector gibt den dem kontrollierten Objekt zugeordneten Speicher automatisch frei, wenn er nicht mehr verwendet wird und keine sichtbaren Verweise darauf vorhanden sind. Es ist jedoch unmöglich vorherzusagen, wann genau die Sammlung stattfinden wird. Garbage Collection (es sei denn, Sie rufen sie manuell auf). Außerdem kennt der Garbage Collector keine nicht verwalteten Ressourcen wie Handles, Fenster oder geöffnete Dateien und Streams. Dispose wird normalerweise verwendet, um solche nicht verwalteten Ressourcen freizugeben. "
Das heißt, wir haben eine erstellt Krypta Variable vom Typ SHA256Managed , welche Geräte die IDisposable - Schnittstelle . Wenn wir die Methode beenden, werden die potenziell erworbenen Ressourcen nicht freigegeben.
Um dies zu verhindern, empfehle ich die Verwendung von . Die Dispose- Methode wird automatisch aufgerufen, wenn sie die mit der using- Anweisung verknüpfte schließende geschweifte Klammer erreicht . Es sieht aus wie das:
public static string ToSHA256(this string data)
{
using (var crypt = new SHA256Managed())
{
var hash = new StringBuilder();
....
}
}
Und wenn Sie keine geschweiften Klammern mögen, können Sie in C # 8.0 folgendermaßen schreiben:
public static string ToSHA256(this string data)
{
using var crypt = new SHA256Managed();
var hash = new StringBuilder();
....
}
Der Unterschied zur vorherigen Option besteht darin, dass die Dispose- Methode aufgerufen wird, wenn die schließende geschweifte Klammer der Methode erreicht ist. Dies ist das Ende der Region, in der die Krypta deklariert ist .
Reale Nummern
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 Das Literal '10 .25 'vom Typ' double 'wird mit einem Wert vom Typ' int 'verglichen. OpeningBreakoutAlgorithm.cs 426
Es sieht seltsam aus, dass unter der Bedingung der Wert einer Variablen vom Typ int mit einem Literal vom Typ double verglichen wird . Es sieht seltsam aus und eine andere Variable fragt eindeutig nach sich selbst. Und tatsächlich, wenn wir uns ansehen, welche Felder mit einem ähnlichen Namen TimeOfDay haben , werden wir finden:
public double TotalHours { get; }
Höchstwahrscheinlich sollte der Code folgendermaßen aussehen:
public bool ShouldPlot
{
get
{
....
if (Time.TimeOfDay.TotalHours < 10.25) return true;
....
}
}
Denken Sie auch daran, dass Sie nicht für direkte Gleichheit ("==", "! =") Gleitkommazahlen vergleichen können. Und vergessen Sie nicht das Typ Casting .
Switch-Anweisung
Tipp 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 Die switch-Anweisung deckt nicht alle Werte der Enum 'TradingDayType': EconomicEvent ab. TradingCalendar.cs 79 Der
Typ der Variablen Typ ist TradingDayType , und dies ist ein Enum :
public enum TradingDayType
{
BusinessDay,
PublicHoliday,
Weekend,
OptionExpiration,
FutureExpiration,
FutureRoll,
SymbolDelisting,
EquityDividends,
EconomicEvent
}
Wenn Sie zählen, werden Sie feststellen, dass die Aufzählung 9 Elemente enthält und nur 8 Elemente im Switch analysiert werden . Eine solche Situation kann aufgrund der Codeerweiterung auftreten. Um dies zu verhindern, empfehle ich immer, die Standardeinstellung explizit zu verwenden :
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);
}
Wie Sie vielleicht bemerkt haben, wurde die return-Anweisung nach dem Wechsel in den Standardabschnitt verschoben . In diesem Fall hat sich die Logik des Programms nicht geändert, aber ich rate Ihnen trotzdem, auf diese Weise zu schreiben.
Der Grund dafür ist die Erweiterbarkeit des Codes. Im Fall des Originals können Sie sicher eine Logik hinzufügen, bevor Sie false zurückgeben , ohne zu vermuten, dass dies die Standardeinstellung der switch-Anweisung ist . Jetzt ist alles klar und offensichtlich.
Wenn Sie jedoch der Meinung sind, dass in Ihrem Fall immer nur ein Teil der Aufzählungselemente verarbeitet werden sollte, können Sie eine Ausnahme auslösen:
default:
throw new CustomExeption("Invalid enumeration element");
Persönlich war ich von diesem syntaktischen C # 8.0-Zucker begeistert:
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
};
};
Tipp 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 Zwei oder mehr Fallzweige führen dieselben Aktionen aus. SecurityCacheTests.cs 510
Zwei verschiedene Fälle geben denselben Wert zurück. In dieser Form sieht es sehr verdächtig aus. Sofort besteht das Gefühl, dass Sie kopiert, eingefügt und vergessen haben, Änderungen vorzunehmen. Daher empfehle ich, dass der Fall wie folgt kombiniert wird, wenn dieselbe Logik für verschiedene Werte ausgeführt werden soll :
public string[] GetPropertiesBy(SecuritySeedData type)
{
switch (type)
{
case SecuritySeedData.None:
return new string[0];
case SecuritySeedData.OpenInterest:
case SecuritySeedData.OpenInterestTick:
return new[] { "OpenInterest" };
....
}
}
Dies zeigt deutlich an, was wir wollen, und entfernt die zusätzliche Textzeile. :) :)
If-Anweisung
Beispiel 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 Ausdruck 'symbol.SecurityType! = SecurityType.Equity || Auflösung! = Auflösung.Täglich || Auflösung! = Auflösung.Stunde 'ist immer wahr. LiveTradingDataFeedTests.cs 1431
Diese Bedingung ist immer erfüllt. Damit die Bedingung nicht erfüllt ist, muss die Auflösungsvariable gleichzeitig den Wert Resolution.Daily und Resolution.Hour haben. Mögliche korrigierte Version:
[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);
}
....
}
Einige Richtlinien für die if-Anweisung . Wenn es eine Bedingung gibt, die vollständig aus den Operatoren "||" besteht, prüfen Sie nach dem Schreiben, ob dieselbe Variable mehrmals hintereinander auf Ungleichheit für etwas geprüft wird .
Ähnlich verhält es sich mit dem Operator "&&". Wenn eine Variable mehrmals auf Gleichheit mit etwas überprüft wird , ist dies höchstwahrscheinlich ein logischer Fehler.
Wenn Sie eine zusammengesetzte Bedingung schreiben und diese "&&" und "||" enthält, zögern Sie nicht, Klammern zu setzen. Dies kann Ihnen helfen, den Fehler entweder zu erkennen oder zu vermeiden.
Beispiel 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 Die Funktion 'Teilzeichenfolge' könnte den Wert '-1' empfangen, während ein nicht negativer Wert erwartet wird. Überprüfen Sie das erste Argument. StringExtensions.cs 311
Der Analysator gibt an, dass der Wert -1 an das erste Argument der Substring- Methode übergeben werden kann . Dies löst eine Ausnahme vom Typ System.ArgumentOutOfRangeException aus . Mal sehen, warum sich ein solcher Wert herausstellen kann. In diesem Beispiel interessieren uns die ersten beiden Bedingungen nicht, daher werden sie in der Begründung weggelassen.
Der Parameter startIndex ist vom Typ intDaher liegen seine Werte im Bereich [-2147483648, 2147483647]. Um ein Überlaufen der Array-Grenzen zu verhindern, hat der Entwickler die folgende Bedingung geschrieben:
if (startIndex < -1)
{
startIndex = 0;
}
Das heißt, es wurde angenommen, dass wenn ein negativer Wert kam, wir ihn einfach auf 0 ändern. Anstelle von "<=" haben wir "<" geschrieben, und jetzt ist die Untergrenze des Bereichs der Variablen startIndex (aus Sicht des Analysators) -1.
Ich schlage vor, in solchen Situationen eine solche Konstruktion zu verwenden:
if (variable < value)
{
variable = value;
}
Diese Kombination ist viel einfacher zu lesen, da es sich um einen Wert weniger handelt. Daher schlage ich vor, das Problem folgendermaßen zu beheben:
public static string SafeSubstring(....)
{
....
if (startIndex < 0)
{
startIndex = 0;
}
return value.Substring(startIndex,
Math.Min(length, value.Length - startIndex));
}
Sie können sagen, dass wir im ersten Beispiel einfach das Vorzeichen in der Bedingung ändern könnten:
if (startIndex <= -1)
{
startIndex = 0;
}
Der Fehler verschwindet ebenfalls. Die Logik sieht jedoch folgendermaßen aus:
if (variable <= value - 1)
{
variable = value;
}
Stimmen Sie zu, dass es überwältigt aussieht.
Beispiel 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 Mögliche Null-Dereferenzierung. Überprüfen Sie "purchasePowerModel". BasicTemplateFuturesAlgorithm.cs 107
V3019 Möglicherweise wird eine falsche Variable nach der Typkonvertierung mit dem Schlüsselwort 'as' mit null verglichen. Überprüfen Sie die Variablen 'purchasePowerModel', 'futureMarginModel'. BasicTemplateFuturesAlgorithm.cs 105
Ein sehr merkwürdiges Stück. Der Analysator generiert zwei Warnungen gleichzeitig. Und tatsächlich enthalten sie das Problem und seine Ursache. Lassen Sie uns zunächst sehen, was passiert, wenn die Bedingung erfüllt ist. Da der Kauf von PowerModel im Inneren streng null ist , erfolgt eine Dereferenzierung:
$"Found: {buyingPowerModel.GetType().Name}. "
Der Grund ist, dass eine Variable in der Bedingung verwechselt wird, die mit null verglichen wird . Anstatt PowerModel zu kaufen, sollte futureMarginModel explizit geschrieben werden . Korrigierte Version:
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)}");
}
....
}
Es bleibt jedoch ein Problem bei der Dereferenzierung von purchasePowerModel innerhalb einer Bedingung. Da futureMarginModel wird null nicht nur , wenn es sich nicht um eine FutureMarginModel , sondern auch , wenn buyingPowerModel ist null . Daher schlage ich diese Option vor:
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)}");
}
....
}
Persönlich habe ich in letzter Zeit das Schreiben solcher Konstrukte mit is geliebt . Dies macht es weniger Code und schwieriger, einen Fehler zu machen. Dieses Beispiel ist dem obigen Beispiel völlig ähnlich:
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (!(buyingPowerModel is FutureMarginModel futureMarginModel))
{
....
}
....
}
Darüber hinaus werden wir in C # 9.0 die Möglichkeit hinzufügen, das Schlüsselwort not zu schreiben :
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (buyingPowerModel is not FutureMarginModel futureMarginModel)
{
....
}
....
}
Beispiel 4
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
V3004 Die Anweisung 'then' entspricht der Anweisung 'else'. FuturesExpiryFunctions.cs 1561
Unter verschiedenen Bedingungen wird dieselbe Logik ausgeführt. Da eines der Argumente ein numerisches Literal ist, muss möglicherweise ein anderer Wert übergeben werden. Zum Beispiel:
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 2);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
Dies ist jedoch nichts weiter als eine Annahme. An dieser Stelle möchte ich Sie darauf aufmerksam machen, dass bei der Initialisierung des Containers ein Fehler auftritt. Die Größe dieser Initialisierung beträgt fast 2000 Zeilen:
Außerdem sind die darin enthaltenen Codefragmente ähnlich, was logisch ist, da die Sammlung hier einfach gefüllt wird. Seien Sie daher besonders vorsichtig, wenn Sie etwas in großen und ähnlichen Bereichen kopieren. Nehmen Sie sofort Änderungen vor, denn dann werden Ihre Augen müde und Sie sehen das Problem nicht.
Beispiel 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 Ausdruck 'request.Parameters.Count> 0' ist immer wahr. GDAXBrokerage.Utility.cs 63 Die
Bedingung im ternären Operator ist immer wahr, da diese Prüfung bereits oben durchgeführt wurde. Dies ist entweder eine redundante Prüfung oder die Operatoren "&&" und "||" sind in der obigen Bedingung verwechselt.
Um dies zu vermeiden, denken Sie in einem Zustand immer daran, welche Werte Sie eingeben werden.
Mögliche korrigierte Version:
public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
....
if (request.Method == Method.GET && request.Parameters.Count > 0)
{
var parameters = string.Join(....);
url = $"{request.Resource}?{parameters}";
}
}
Beispiel 6
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan == UserPlan.Free)
{
MaxOrders = 10000;
}
else
{
MaxOrders = int.MaxValue;
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders; // <=
....
}
V3008 Der Variablen 'MaxOrders' werden zweimal hintereinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 244, 240. BacktestingSetupHandler.cs 244
Hier wird der MaxOrders- Variablen zweimal hintereinander ein Wert zugewiesen. Das heißt, Logik mit Bedingungen ist redundant.
Um dies zu beheben, haben wir 2 Möglichkeiten. Wir entfernen entweder die Zuordnungen in den Zweigen then-else oder die Zuordnung nach der Bedingung. Höchstwahrscheinlich wird der Code durch Tests abgedeckt und das Programm funktioniert ordnungsgemäß. Daher verlassen wir nur die letzte Aufgabe. Mögliche korrigierte Version:
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan != UserPlan.Free)
{
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders;
....
}
Häufige Fehler für Menschen
Fehler wie Kopieren und Einfügen, versehentlich gedrückte Tasten usw. werden hier berücksichtigt. Im Allgemeinen sind die häufigsten Probleme der menschlichen Unvollkommenheit. Wir sind keine Maschinen, daher sind solche Situationen normal.
Allgemeine Empfehlungen für sie:
- Wenn Sie etwas kopieren, nehmen Sie Änderungen an der Kopie vor, sobald Sie sie einfügen.
- eine Codeüberprüfung durchführen;
- Verwenden Sie spezielle Tools, die nach Fehlern für Sie suchen.
Situation 1
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMax.IsReady && _medianMax.IsReady;
}
V3001 Links und rechts vom Operator '&&' befinden sich identische Unterausdrücke '_medianMax.IsReady'. FisherTransform.cs 72
In diesem Beispiel muss das IsReady- Feld von zwei Bedingungen abhängen, tatsächlich jedoch von einer. Es ist alles die Schuld eines Tippfehlers. Höchstwahrscheinlich sie schrieb _medianMax statt _medianMin . Korrigierte Version:
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMin.IsReady && _medianMax.IsReady;
}
Situation 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 Der Variablen 'SessionId' werden zweimal nacheinander Werte zugewiesen. Vielleicht ist das ein Fehler. Überprüfen Sie die Zeilen: 182, 172. BacktestResultPacket.cs 182
Die Klasse verfügt über viele Felder, die initialisiert werden müssen - viele Zeilen im Konstruktor. Alles wird zusammengeführt und ein Feld wird mehrmals initialisiert. In diesem Fall liegt möglicherweise eine unnötige Initialisierung vor oder sie haben vergessen, ein anderes Feld zu initialisieren.
Wenn Sie interessiert sind, können Sie sich andere Fehler ansehen , die von dieser Diagnoseregel gefunden wurden.
Situation 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 Empirische Analyse. Es ist möglich, dass im String-Literal ein Tippfehler vorhanden ist. Das Wort "Punktzahl" ist verdächtig. InsightJsonConverterTests.cs 209
Entschuldigen Sie den großen und beängstigenden Code. Verschiedene Felder haben hier die gleichen Werte. Dies ist ein klassischer Fehler aus der Copy-Paste-Familie. Kopiert, nachdenklich, vergessen, Änderungen vorzunehmen - das ist der Fehler.
Situation 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 Die Operation '-' wird zwei- oder mehrmals hintereinander ausgeführt. Überprüfen Sie den Ausdruck '- -shares'. OpeningBreakoutAlgorithm.cs 328 Unärer
"-" - Operator, der zweimal hintereinander angewendet wird. Somit bleibt der an die MarketOrder- Methode übergebene Wert unverändert. Es ist sehr schwer zu sagen, wie viele unäre Nachteile hier verbleiben sollten. Vielleicht ist das Präfix Dekrementoperator „-“ ist hier in der Regel erforderlich, aber der Raum Schlüssel versehentlich getroffen . Die Optionen sind dunkel, daher eine der möglichen korrigierten Optionen:
private void ScanForEntrance()
{
....
if (ShouldEnterLong)
{
MarketTicket = MarketOrder(symbol, shares);
....
}
else if (ShouldEnterShort)
{
MarketTicket = MarketOrder(symbol, -shares);
....
StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
....
}
....
}
Situation 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 Die Variable '_factory' wird sich selbst zugewiesen. ZipEntryNameSubscriptionDataSourceReader.cs 50
Paul _factory wird zweimal der gleiche Wert zugewiesen. Es gibt nur vier Felder in der Klasse, daher ist dies höchstwahrscheinlich nur ein Tippfehler. Korrigierte Version:
public ZipEntryNameSubscriptionDataSourceReader(....)
{
_config = config;
_date = date;
_isLiveMode = isLiveMode;
_factory = config.GetBaseDataInstance();
}
Fazit
Es gibt viele Orte, an denen Sie Fehler machen können. Einige bemerken und korrigieren wir sofort. Ein Teil davon wurde für die Codeüberprüfung korrigiert, und ich empfehle, einen Teil Spezialwerkzeugen zuzuweisen.
Wenn Ihnen dieses Format gefallen hat, schreiben Sie bitte darüber. Ich werde noch etwas Ähnliches tun. Vielen Dank!
Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Übersetzungslink: Nikolay Mironov. Über Fehler im QuantConnect Lean Code sprechen .