Artikel ini membahas bug dalam proyek open source yang ditemukan menggunakan penganalisis statis. Berikut beberapa hal sederhana yang dapat membantu Anda menghindarinya. Misalnya, menggunakan konstruksi bahasa sintaksis sejak C # 8.0. Semoga menarik. Selamat membaca.
QuantConnect Lean adalah mesin perdagangan algoritmik sumber terbuka yang dibuat untuk penelitian strategi yang mudah, pengujian ulang, dan perdagangan langsung. Kompatibel dengan Windows, Linux dan macOS. Terintegrasi dengan penyedia data arus utama dan perusahaan pialang untuk menerapkan strategi perdagangan algoritmik dengan cepat.
Pengecekan dilakukan dengan menggunakan alat analisa statis PVS-Studio . PVS-Studio adalah alat untuk mengidentifikasi kesalahan dan potensi kerentanan dalam kode sumber program yang ditulis dalam C, C ++, C # dan Java pada Windows, Linux, dan macOS.
Kecelakaan bukanlah kecelakaan
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 Ekspresi '_random.Next (0, 1) == 0' selalu benar. RandomValueGenerator.cs 142 Intinya
adalah bahwa salah satu nilai atau lainnya dipilih dengan probabilitas 50%. Namun, dalam kasus ini, metode Berikutnya akan selalu mengembalikan 0.
Ini karena rentang tidak menyertakan argumen kedua. Artinya, nilai yang dapat dikembalikan metode akan berada dalam kisaran [0,1). Mari perbaiki ini:
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;
}
....
}
Meneruskan Parameter Jenis Referensi
Contoh
/// <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 Parameter 'array' selalu ditulis ulang dalam badan metode sebelum digunakan. SecurityPortfolioManager.cs 192
Metode ini mengambil koleksi dan segera menimpa nilainya. Setuju bahwa ini terlihat agak mencurigakan. Jadi mari kita coba memahami apa yang harus dilakukan metode ini.
Dari komentar dan nama metode, menjadi jelas bahwa beberapa array lain harus disalin ke array yang dilewati. Namun, ini tidak akan terjadi, dan nilai array di luar metode saat ini tidak akan berubah.
Ini terjadi karena argumen arrayakan diteruskan ke metode berdasarkan nilai, bukan dengan referensi. Jadi, setelah melakukan operasi penugasan, referensi ke objek baru akan disimpan oleh array variabel , tersedia di dalam metode. Nilai argumen yang diteruskan ke metode akan tetap tidak berubah. Untuk memperbaikinya, Anda harus meneruskan argumen tipe referensi dengan referensi:
public void CopyTo(KeyValuePair<Symbol, SecurityHolding>[] out array, // <=
int index)
{
array = new KeyValuePair<Symbol, SecurityHolding>[Securities.Count];
....
}
Karena kita pasti menulis larik baru dalam metode ini, kita menggunakan pengubah keluar daripada ref . Ini segera berarti bahwa variabel akan diberi nilai di dalamnya.
Ngomong-ngomong, kasus ini mengisi ulang koleksi yang dikumpulkan kolega saya Andrey Karpov dan yang dapat Anda pelajari dari artikel " Memulai Mengumpulkan Kesalahan dalam Fungsi Salinan ".
Membebaskan sumber daya
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 ID objek sekali pakai 'crypt' tidak dibuang sebelum metode kembali. Extensions.cs 510
Untuk memahami arti diagnostik ini, pertama-tama mari kita ingat sedikit teorinya. Dengan izin Anda, saya akan mengambil informasi dari dokumentasi untuk diagnostik ini:
"Pengumpul sampah secara otomatis membebaskan memori yang terkait dengan objek yang dipantau ketika tidak lagi digunakan dan tidak ada referensi yang terlihat ke sana. Namun, tidak mungkin untuk memprediksi dengan tepat kapan pengumpulan akan terjadi. pengumpulan sampah (kecuali jika Anda memanggilnya secara manual.) Selain itu, pengumpul sampah tidak memiliki pengetahuan tentang sumber daya yang tidak dikelola seperti pegangan, jendela, atau file dan aliran yang terbuka. Metode Buang biasanya digunakan untuk melepaskan sumber daya yang tidak dikelola tersebut. "
Artinya, kami telah membuat variabel crypt tipe SHA256Managed , yang mengimplementasikan antarmuka IDisposable . Akibatnya, saat kami keluar dari metode ini, sumber daya yang berpotensi diperoleh tidak akan dirilis.
Untuk mencegahnya, saya sarankan menggunakan . The Buang metode akan dipanggil secara otomatis ketika penjepit keriting menutup terkait dengan menggunakan pernyataan tercapai . Ini terlihat seperti ini:
public static string ToSHA256(this string data)
{
using (var crypt = new SHA256Managed())
{
var hash = new StringBuilder();
....
}
}
Dan jika Anda tidak suka kurung kurawal, maka di C # 8.0 Anda bisa menulis seperti ini:
public static string ToSHA256(this string data)
{
using var crypt = new SHA256Managed();
var hash = new StringBuilder();
....
}
Perbedaan dari opsi sebelumnya adalah bahwa metode Buang dipanggil saat kurung kurawal tutup metode tercapai. Ini adalah akhir dari wilayah tempat crypt dideklarasikan .
Bilangan real
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 Literal '10 .25 'dari tipe' double 'dibandingkan dengan nilai tipe' int '. OpeningBreakoutAlgorithm.cs 426
Terlihat aneh bahwa pada kondisi nilai variabel bertipe int dibandingkan dengan literal bertipe double . Itu terlihat aneh dan beberapa variabel lain dengan jelas menanyakan dirinya sendiri. Dan, memang, jika kita melihat bidang apa dengan nama serupa yang dimiliki TimeOfDay , kita akan menemukan:
public double TotalHours { get; }
Kemungkinan besar kodenya akan terlihat seperti ini:
public bool ShouldPlot
{
get
{
....
if (Time.TimeOfDay.TotalHours < 10.25) return true;
....
}
}
Juga ingat bahwa Anda tidak dapat membandingkan untuk persamaan langsung ("==", "! =") Angka floating point. Dan jangan lupa tentang jenis casting .
Ganti pernyataan
Tip 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 Pernyataan switch tidak mencakup semua nilai enum 'TradingDayType': EconomicEvent. TradingCalendar.cs 79
jenis variabel jenis ini TradingDayType , dan ini merupakan enum :
public enum TradingDayType
{
BusinessDay,
PublicHoliday,
Weekend,
OptionExpiration,
FutureExpiration,
FutureRoll,
SymbolDelisting,
EquityDividends,
EconomicEvent
}
Jika Anda menghitung, Anda akan melihat bahwa terdapat 9 elemen dalam pencacahan, dan hanya 8 elemen yang dianalisis dalam sakelar.Kondisi ini dapat terjadi karena perluasan kode. Untuk mencegah hal ini, saya selalu merekomendasikan penggunaan default secara eksplisit :
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);
}
Seperti yang mungkin telah Anda perhatikan, pernyataan return setelah sakelar dipindahkan ke bagian default . Dalam hal ini, logika program tidak berubah, tetapi saya tetap menyarankan Anda untuk menulis dengan cara ini.
Alasannya adalah ekstensibilitas kode. Dalam kasus asli, Anda dapat dengan aman menambahkan beberapa logika sebelum kembali palsu , tidak mencurigai bahwa ini adalah default yang pernyataan switch . Sekarang semuanya jelas dan jelas.
Namun, jika Anda berpikir bahwa dalam kasus Anda hanya sebagian dari elemen enumerasi yang harus selalu diproses, Anda dapat membuat pengecualian:
default:
throw new CustomExeption("Invalid enumeration element");
Secara pribadi, saya terpikat pada gula sintaksis C # 8.0 ini:
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
};
};
Tip 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 Dua atau lebih cabang kasus melakukan tindakan yang sama. SecurityCacheTests.cs 510
Dua kasus berbeda mengembalikan nilai yang sama. Dalam bentuk ini, terlihat sangat mencurigakan. Segera Anda merasa bahwa Anda menyalin, menempel, dan lupa untuk berubah. Oleh karena itu, saya merekomendasikan bahwa jika logika yang sama harus dilakukan untuk nilai yang berbeda, maka gabungkan case seperti ini:
public string[] GetPropertiesBy(SecuritySeedData type)
{
switch (type)
{
case SecuritySeedData.None:
return new string[0];
case SecuritySeedData.OpenInterest:
case SecuritySeedData.OpenInterestTick:
return new[] { "OpenInterest" };
....
}
}
Ini dengan jelas menunjukkan apa yang kita inginkan, ditambah itu menghapus baris teks tambahan. :)
Jika pernyataan
Contoh 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);
}
....
}
Simbol Ekspresi V3022'.SecurityType! = SecurityType.Equity || resolusi! = Resolusi. Setiap hari || resolusi! = Resolution.Hour 'selalu benar. LiveTradingDataFeedTests.cs 1431
Kondisi ini selalu benar. Lagipula, agar kondisi tidak terpenuhi, variabel resolusi harus memiliki nilai Resolution.Daily dan Resolution.Hour pada waktu yang bersamaan. Versi yang mungkin diperbaiki:
[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);
}
....
}
Beberapa pedoman untuk pernyataan if . Ketika ada kondisi yang seluruhnya terdiri dari operator "||", maka setelah menulis, periksa apakah variabel yang sama tidak diperiksa ketidaksamaannya dengan sesuatu beberapa kali berturut-turut.
Situasinya serupa dengan operator "&&". Jika suatu variabel diperiksa beberapa kali untuk persamaan dengan sesuatu, maka kemungkinan besar ini adalah kesalahan logis.
Juga, jika Anda menulis kondisi majemuk, dan mengandung "&&" dan "||", jangan ragu untuk memberi tanda kurung. Ini dapat membantu Anda melihat kesalahan atau menghindarinya.
Contoh 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 Fungsi 'Substring' dapat menerima nilai '-1' sementara nilai non-negatif diharapkan. Periksa argumen pertama. StringExtensions.cs 311
Penganalisis mengatakan bahwa nilai -1 dapat diteruskan ke argumen pertama metode Substring . Ini akan memunculkan pengecualian tipe System.ArgumentOutOfRangeException . Mari kita lihat mengapa nilai seperti itu bisa berubah. Dalam contoh ini, kami tidak tertarik pada dua kondisi pertama, jadi mereka akan dihilangkan dalam penalaran.
Parameter startIndex berjenis intoleh karena itu, nilainya berada dalam kisaran [-2147483648, 2147483647]. Oleh karena itu, untuk mencegah meluapnya batas larik, pengembang menulis kondisi berikut:
if (startIndex < -1)
{
startIndex = 0;
}
Artinya, diasumsikan bahwa jika ada nilai negatif, maka kita cukup mengubahnya menjadi 0. Tetapi alih-alih "<=" kita menulis "<", dan sekarang batas bawah kisaran variabel startIndex (dari sudut pandang penganalisis) adalah -1.
Saya menyarankan menggunakan konstruksi seperti ini dalam situasi seperti ini:
if (variable < value)
{
variable = value;
}
Kombinasi ini jauh lebih mudah dibaca, karena hanya ada satu nilai yang lebih sedikit. Oleh karena itu, saya mengusulkan untuk memperbaiki masalah seperti ini:
public static string SafeSubstring(....)
{
....
if (startIndex < 0)
{
startIndex = 0;
}
return value.Substring(startIndex,
Math.Min(length, value.Length - startIndex));
}
Anda dapat mengatakan bahwa dalam contoh awal kita cukup mengubah tanda dalam kondisi:
if (startIndex <= -1)
{
startIndex = 0;
}
Kesalahan juga hilang. Namun, logikanya akan terlihat seperti ini:
if (variable <= value - 1)
{
variable = value;
}
Setuju bahwa itu terlihat kewalahan.
Contoh 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 Kemungkinan dereferensi nol. Pertimbangkan untuk memeriksa 'buyingPowerModel'. BasicTemplateFuturesAlgorithm.cs 107
V3019 Mungkin variabel yang salah dibandingkan dengan null setelah konversi jenis menggunakan kata kunci 'as'. Periksa variabel 'buyingPowerModel', 'futureMarginModel'. BasicTemplateFuturesAlgorithm.cs 105
Bagian yang sangat menarik. Penganalisis menghasilkan dua peringatan sekaligus. Dan nyatanya, mereka mengandung masalah dan penyebabnya. Pertama, mari kita lihat apa yang terjadi jika syaratnya terpenuhi. Karena buyingPowerModel di dalam akan benar-benar nol , dereferensi akan terjadi:
$"Found: {buyingPowerModel.GetType().Name}. "
Alasannya adalah variabel tercampur dalam kondisi, yang dibandingkan dengan nol . Alih-alih buyingPowerModel, futureMarginModel harus ditulis secara eksplisit . Versi yang diperbaiki:
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)}");
}
....
}
Namun, masih ada masalah dengan dereferensi buyingPowerModel di dalam suatu kondisi. Karena futureMarginModel akan menjadi null tidak hanya jika bukan FutureMarginModel , tetapi juga saat buyingPowerModel bernilai null . Oleh karena itu, saya menyarankan opsi ini:
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)}");
}
....
}
Secara pribadi, akhir-akhir ini saya menjadi suka menulis konstruksi seperti itu menggunakan is . Ini membuatnya lebih sedikit kode dan lebih sulit untuk membuat kesalahan. Contoh ini sangat mirip dengan contoh di atas:
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (!(buyingPowerModel is FutureMarginModel futureMarginModel))
{
....
}
....
}
Selain itu, di C # 9.0 kami akan menambahkan kemampuan untuk menulis bukan kata kunci :
public override void OnEndOfAlgorithm()
{
var buyingPowerModel = Securities[_contractSymbol].BuyingPowerModel;
if (buyingPowerModel is not FutureMarginModel futureMarginModel)
{
....
}
....
}
Contoh 4
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
V3004 Pernyataan 'then' setara dengan pernyataan 'else'. FuturesExpiryFunctions.cs 1561
Dalam kondisi yang berbeda, logika yang sama dijalankan. Karena salah satu argumen adalah literal numerik, nilai yang berbeda mungkin harus diteruskan. Sebagai contoh:
public static readonly Dictionary<....>
FuturesExpiryDictionary = new Dictionary<....>()
{
....
if (twoMonthsPriorToContractMonth.Month == 2)
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 2);
}
else
{
lastBusinessDay = FuturesExpiryUtilityFunctions
.NthLastBusinessDay(twoMonthsPriorToContractMonth, 1);
}
....
}
Tapi ini tidak lebih dari asumsi. Di sini saya ingin menarik perhatian Anda pada fakta bahwa kesalahan terjadi dalam inisialisasi penampung. Ukuran inisialisasi ini hampir 2000 baris:
Selain itu, fragmen kode di dalamnya mirip satu sama lain, yang logis, karena koleksi hanya diisi di sini. Oleh karena itu, berhati-hatilah saat menyalin sesuatu di area yang luas dan serupa. Segera lakukan perubahan, karena mata Anda akan lelah dan Anda tidak akan melihat masalahnya.
Contoh 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 Expression 'request.Parameters.Count> 0' selalu benar. GDAXBrokerage.Utility.cs 63
Kondisi pada operator terner selalu benar karena pemeriksaan ini sudah dilakukan di atas. Sekarang ini adalah cek yang berlebihan, atau operator "&&" dan "||" tercampur dalam kondisi di atas.
Untuk menghindari hal tersebut, ketika Anda sedang dalam kondisi, selalu perhatikan nilai apa yang akan Anda masukkan.
Versi yang mungkin diperbaiki:
public AuthenticationToken GetAuthenticationToken(IRestRequest request)
{
....
if (request.Method == Method.GET && request.Parameters.Count > 0)
{
var parameters = string.Join(....);
url = $"{request.Resource}?{parameters}";
}
}
Contoh 6
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan == UserPlan.Free)
{
MaxOrders = 10000;
}
else
{
MaxOrders = int.MaxValue;
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders; // <=
....
}
V3008 Variabel 'MaxOrders' diberi nilai dua kali berturut-turut. Mungkin ini salah. Periksa baris: 244, 240. BacktestingSetupHandler.cs 244
Di sini, variabel MaxOrders diberi nilai dua kali berturut-turut. Artinya, logika dengan kondisi berlebihan.
Untuk mengatasinya, kami memiliki 2 opsi. Kami menghapus tugas di cabang lalu-lain, atau tugas setelah kondisi. Kemungkinan besar, kode tersebut tercakup dalam tes, dan program bekerja dengan benar. Oleh karena itu, kami hanya akan meninggalkan tugas terakhir. Versi yang mungkin diperbaiki:
public bool Setup(SetupHandlerParameters parameters)
{
....
if (job.UserPlan != UserPlan.Free)
{
MaximumRuntime += MaximumRuntime;
}
MaxOrders = job.Controls.BacktestingMaxOrders;
....
}
Kesalahan umum bagi manusia
Kesalahan seperti salin-tempel, tombol yang tidak sengaja ditekan, dll. Akan dipertimbangkan di sini. Secara umum, masalah ketidaksempurnaan manusia yang paling umum. Kami bukan mesin, jadi situasi ini normal.
Rekomendasi umum untuk mereka:
- jika Anda menyalin sesuatu, buat perubahan pada salinan tersebut segera setelah Anda menempelkannya;
- melakukan tinjauan kode;
- gunakan alat khusus yang akan mencari kesalahan untuk Anda.
Situasi 1
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMax.IsReady && _medianMax.IsReady;
}
V3001 Ada sub-ekspresi identik '_medianMax.IsReady' di kiri dan kanan operator '&&'. FisherTransform.cs 72
Dalam contoh ini, kolom IsReady harus bergantung pada dua kondisi, tetapi sebenarnya bergantung pada satu kondisi. Itu semua karena salah ketik. Kemungkinan besar, mereka menulis _medianMax, bukan _medianMin . Versi yang diperbaiki:
public class FisherTransform : BarIndicator, IIndicatorWarmUpPeriodProvider
{
private readonly Minimum _medianMin;
private readonly Maximum _medianMax;
public override bool IsReady => _medianMin.IsReady && _medianMax.IsReady;
}
Situasi 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 Variabel 'SessionId' diberi nilai dua kali berturut-turut. Mungkin ini salah. Periksa baris: 182, 172. BacktestResultPacket.cs 182
Kelas memiliki banyak bidang yang perlu diinisialisasi - banyak baris dalam konstruktor. Semuanya digabungkan dan satu bidang diinisialisasi beberapa kali. Dalam kasus ini, mungkin ada inisialisasi yang tidak perlu, atau mereka lupa menginisialisasi bidang lain.
Jika Anda tertarik, Anda dapat melihat kesalahan lain yang ditemukan oleh aturan diagnostik ini.
Situasi 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 Analisis empiris. Ada kemungkinan salah ketik ada di dalam string literal. Kata 'skor' itu mencurigakan. InsightJsonConverterTests.cs 209
Maaf untuk kode yang besar dan menakutkan. Bidang yang berbeda memiliki nilai yang sama di sini. Ini adalah kesalahan klasik dari keluarga salin-tempel. Disalin, bijaksana, lupa melakukan perubahan - itulah kesalahannya.
Situasi 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 Operasi '-' dijalankan 2 kali atau lebih secara berurutan. Pertimbangkan untuk memeriksa ekspresi '- -shares'. OpeningBreakoutAlgorithm.cs 328 Operator
"-" unary diterapkan dua kali berturut-turut. Dengan demikian, nilai yang diteruskan ke metode MarketOrder akan tetap tidak berubah. Sangat sulit untuk mengatakan berapa banyak kontra unary yang harus ditinggalkan di sini. Mungkin operator pengurangan prefiks "-" biasanya dibutuhkan di sini, tetapi tombol spasi tidak sengaja terpukul . Opsinya gelap, jadi salah satu opsi yang mungkin diperbaiki:
private void ScanForEntrance()
{
....
if (ShouldEnterLong)
{
MarketTicket = MarketOrder(symbol, shares);
....
}
else if (ShouldEnterShort)
{
MarketTicket = MarketOrder(symbol, -shares);
....
StopLossTicket = StopMarketOrder(symbol, -shares, stopPrice);
....
}
....
}
Situasi 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 Variabel '_factory' ditetapkan ke dirinya sendiri. ZipEntryNameSubscriptionDataSourceReader.cs 50
Paul _factory dua kali diberi nilai yang sama. Hanya ada empat bidang di kelas, jadi kemungkinan besar ini hanya salah ketik. Versi yang diperbaiki:
public ZipEntryNameSubscriptionDataSourceReader(....)
{
_config = config;
_date = date;
_isLiveMode = isLiveMode;
_factory = config.GetBaseDataInstance();
}
Kesimpulan
Ada banyak tempat di mana Anda bisa membuat kesalahan. Beberapa kami perhatikan dan segera perbaiki. Sebagian dikoreksi untuk peninjauan kode, dan saya sarankan untuk menetapkan sebagian ke alat khusus.
Juga, jika Anda menyukai format ini, silakan tulis tentang itu. Saya akan melakukan hal serupa lainnya. Terima kasih!
Jika Anda ingin berbagi artikel ini dengan audiens berbahasa Inggris, silakan gunakan tautan terjemahan: Nikolay Mironov. Berbicara Tentang Kesalahan dalam Kode Lean QuantConnect .