Publikasi kode sumber Microsoft untuk proyek-proyeknya adalah alasan yang bagus untuk memverifikasinya. Kali ini tidak terkecuali, dan hari ini kita melihat tempat mencurigakan yang ditemukan dalam kode Infer.NET. Turun dengan anotasi - langsung ke intinya!
Sedikit tentang proyek dan penganalisa
Infer.NET adalah sistem pembelajaran mesin yang dikembangkan oleh para spesialis dari Microsoft. Kode sumber proyek baru-baru ini tersedia di
GitHub , yang merupakan alasan untuk verifikasi. Rincian lebih lanjut tentang proyek ini dapat ditemukan, misalnya, di
sini .
Proyek ini diperiksa dengan menggunakan statis static analyzer PVS-Studio versi 6.26. Biarkan saya mengingatkan Anda bahwa PVS-Studio sedang mencari kesalahan dalam kode di C \ C ++ \ C # (dan segera di Jawa) di bawah Windows, Linux, macOS. Kode C # sejauh ini kami hanya menganalisis di bawah Windows. Penganalisa dapat
diunduh dan dicoba pada proyek Anda.
Cek itu sendiri sangat sederhana dan tanpa masalah. Sebelumnya, saya membongkar proyek dari GitHub, memulihkan paket yang diperlukan (dependensi), dan memastikan bahwa proyek tersebut berhasil dibangun. Ini diperlukan agar penganalisa memiliki akses ke semua informasi yang diperlukan untuk analisis penuh. Setelah berkumpul dalam beberapa klik, saya meluncurkan analisis solusi melalui plugin PVS-Studio untuk Visual Studio.
Omong-omong, ini bukan proyek pertama dari Microsoft yang diuji menggunakan PVS-Studio - ada yang lain:
Roslyn ,
MSBuild ,
PowerShell ,
CoreFX dan
lainnya .
Catatan Jika Anda atau kenalan Anda tertarik untuk menganalisis kode Java, Anda dapat menulis
dukungan kepada kami dengan memilih "Saya ingin penganalisis untuk Java". Tidak ada versi beta publik dari penganalisis, tetapi harus segera tersedia. Di suatu tempat di laboratorium rahasia (melalui dinding), para pria secara aktif mengerjakannya.
Tapi cukup bicara abstrak - mari kita lihat masalah dalam kode.
Apakah ini bug atau fitur?
Saya sarankan mencoba menemukan kesalahan sendiri - tugas yang sepenuhnya bisa diselesaikan. Tidak ada lelucon dalam semangat apa yang ada dalam artikel "
10 Kesalahan Teratas dalam Proyek C ++ untuk 2017 ", jujur. Jadi, jangan buru-buru membaca penganalisis yang diberikan setelah cuplikan kode.
private void MergeParallelTransitions() { .... if ( transition1.DestinationStateIndex == transition2.DestinationStateIndex && transition1.Group == transition2.Group) { if (transition1.IsEpsilon && transition2.IsEpsilon) { .... } else if (!transition1.IsEpsilon && !transition2.IsEpsilon) { .... if (double.IsInfinity(transition1.Weight.Value) && double.IsInfinity(transition1.Weight.Value)) { newElementDistribution.SetToSum( 1.0, transition1.ElementDistribution, 1.0, transition2.ElementDistribution); } else { newElementDistribution.SetToSum( transition1.Weight.Value, transition1.ElementDistribution, transition2.Weight.Value, transition2.ElementDistribution); } .... }
Peringatan PVS-Studio :
V3001 Ada sub-ekspresi identik 'ganda.IsInfinity (transition1Weight.Value)' di sebelah kiri dan di sebelah kanan operator '&&'. Runtime Automaton.Simplification.cs 479
Seperti yang Anda lihat dari potongan kode, metode ini bekerja dengan sepasang variabel -
transition1 dan
transition2 . Penggunaan nama-nama yang mirip kadang-kadang cukup dibenarkan, tetapi perlu diingat bahwa dalam kasus ini kemungkinan tidak sengaja membuat kesalahan di suatu tempat dengan peningkatan nama.
Inilah
yang terjadi ketika memeriksa angka untuk infinity (
double.IsInfinity ). Karena kesalahan, kami memeriksa nilai variabel yang sama 2 kali -
transition1Weight.Value . Nilai yang dicentang di subekspresi kedua adalah variabel
transition2Weight.Value .
Kode mencurigakan serupa lainnya.
internal MethodBase ToMethodInternal(IMethodReference imr) { .... bf |= BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; .... }
PVS-Studio Warning :
V3001 Ada sub-ekspresi yang identik 'BindingFlags.Public' di sebelah kiri dan di sebelah kanan '|' operator. Kompiler CodeBuilder.cs 194
Saat membentuk nilai variabel
bf , elemen enumerasi
BindingFlags.Public digunakan dua kali. Entah kode ini berisi operasi penandaan tambahan, atau alih-alih penggunaan
BindingFlags.Public yang kedua
, harus ada nilai enumerasi yang berbeda.
Omong-omong, dalam kode sumber kode ini ditulis pada satu baris. Sepertinya saya bahwa jika diformat dalam gaya tabel (seperti di sini), masalahnya lebih mudah untuk dideteksi.
Mari kita lanjutkan. Saya membawa seluruh tubuh metode dan sekali lagi saya sarankan Anda menemukan kesalahan (atau mungkin kesalahan) sendiri.
private void ForEachPrefix(IExpression expr, Action<IExpression> action) {
Apakah kamu sudah menemukan? Kami sedang memeriksa!
Peringatan PVS-Studio :
- V3003 Penggunaan pola 'jika (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 1719, 1727. Kompiler CodeRecognizer.cs 1719
- V3003 Penggunaan pola 'jika (A) {...} else jika (A) {...}' terdeteksi. Ada kemungkinan kehadiran kesalahan logis. Periksa baris: 1721, 1729. Kompiler CodeRecognizer.cs 1721
Sederhanakan kode ini untuk membuat masalahnya lebih jelas.
private void ForEachPrefix(IExpression expr, Action<IExpression> action) { if (....) .... else if (expr is IUnaryExpression) ForEachPrefix(((IUnaryExpression)expr).Expression, action); else if (expr is IAddressReferenceExpression) ForEachPrefix(((IAddressReferenceExpression)expr).Expression, action); .... else if (expr is IUnaryExpression) ForEachPrefix(((IUnaryExpression)expr).Expression, action); else if (expr is IAddressReferenceExpression) ForEachPrefix(((IAddressReferenceExpression)expr).Expression, action) .... }
Ekspresi bersyarat dan
kemudian cabang beberapa
jika pernyataan digandakan. Mungkin kode ini ditulis menggunakan metode salin-rekat, itulah sebabnya masalah muncul. Sekarang ternyata cabang saat itu dari duplikat tidak akan pernah dieksekusi, karena:
- jika ekspresi kondisional benar, tubuh pernyataan if pertama dari pasangan yang sesuai dijalankan;
- jika ekspresi kondisional salah dalam kasus pertama, itu akan salah dalam yang kedua.
Karena cabang saat itu berisi tindakan yang sama, sekarang terlihat seperti kode yang berlebihan yang membingungkan. Ada kemungkinan bahwa ini adalah jenis masalah yang berbeda - alih-alih mengambil, pemeriksaan lain seharusnya dilakukan.
Kami melanjutkan.
public int Compare(Pair<int, int> x, Pair<int, int> y) { if (x.First < y.First) { if (x.Second >= y.Second) {
Peringatan PVS-Studio :
- V3004 Pernyataan 'lalu' setara dengan pernyataan 'lain'. Runtime RegexpTreeBuilder.cs 1080
- V3004 Pernyataan 'lalu' setara dengan pernyataan 'lain'. Runtime RegexpTreeBuilder.cs 1093
Kode terlihat sangat mencurigakan, karena berisi dua pernyataan bersyarat dengan badan yang identik dari cabang
saat itu dan yang
lain . Dalam kedua kasus, mungkin layak mengembalikan nilai yang berbeda. Atau, jika ini adalah perilaku yang dikandung, akan berguna untuk menghapus pernyataan bersyarat yang berlebihan.
Ada siklus yang menarik. Contoh di bawah ini:
private static Set<StochasticityPattern> IntersectPatterns(IEnumerable<StochasticityPattern> patterns) { Set<StochasticityPattern> result = new Set<StochasticityPattern>(); result.AddRange(patterns); bool changed; do { int count = result.Count; AddIntersections(result); changed = (result.Count != count); break; } while (changed); return result; }
PVS-Studio Warning :
V3020 'break' tanpa syarat dalam satu loop. Kompiler DefaultFactorManager.cs 474
Karena pernyataan
break tanpa syarat, tepat satu iterasi dari loop dilakukan, dan variabel kontrol
berubah bahkan tidak digunakan. Secara umum, kode itu terlihat aneh dan mencurigakan.
Metode yang sama (salinan persis) ditemukan di kelas lain. Peringatan analisa yang sesuai:
V3020 'Istirahat' tanpa syarat dalam satu loop. Visualizers.Windows FactorManagerView.cs 350
Ngomong-ngomong, sebuah metode bertemu dengan pernyataan
terus tanpa syarat dalam satu lingkaran (penganalisa menemukannya dengan diagnostik yang sama), tetapi ada komentar di atasnya yang mengkonfirmasi bahwa ini adalah solusi sementara khusus:
Saya ingat bahwa tidak ada komentar semacam itu di dekat pernyataan
break tanpa syarat.
Mari kita lanjutkan.
internal static DependencyInformation GetDependencyInfo(....) { .... IExpression resultIndex = null; .... if (resultIndex != null) { if (parameter.IsDefined( typeof(SkipIfMatchingIndexIsUniformAttribute), false)) { if (resultIndex == null) throw new InferCompilerException( parameter.Name + " has SkipIfMatchingIndexIsUniformAttribute but " + StringUtil.MethodNameToString(method) + " has no resultIndex parameter"); .... } .... } .... }
Peringatan PVS-Studio : Ekspresi V3022 'resultIndex == null' selalu salah. Compiler FactorManager.cs 382
Segera, saya perhatikan bahwa antara deklarasi dan verifikasi di atas, nilai variabel
resultIndex dapat berubah. Namun, antara cek
resultIndex! = Null dan
resultIndex == null, nilainya tidak dapat diubah. Oleh karena itu, hasil dari ekspresi
resultIndex == null akan selalu
salah , yang berarti bahwa pengecualian tidak akan pernah dilempar.
Saya harap Anda memiliki minat dalam menemukan kesalahan sendiri, tanpa saran saya, untuk menemukan masalah, tetapi untuk berjaga-jaga, saya akan mengusulkan untuk melakukannya lagi. Kode metode kecil, saya akan memberikannya secara keseluruhan.
public static Tuple<int, string> ComputeMovieGenre(int offset, string feature) { string[] genres = feature.Split('|'); if (genres.Length < 1 && genres.Length > 3) { throw new ArgumentException(string.Format( "Movies should have between 1 and 3 genres; given {0}.", genres.Length)); } double value = 1.0 / genres.Length; var result = new StringBuilder( string.Format( "{0}:{1}", offset + MovieGenreBuckets[genres[0]], value)); for (int i = 1; i < genres.Length; ++i) { result.Append( string.Format( "|{0}:{1}", offset + MovieGenreBuckets[genres[i].Trim()], value)); } return new Tuple<int, string>(MovieGenreBucketCount, result.ToString()); }
Mari kita lihat apa yang terjadi di sini. String input diuraikan oleh karakter '|'. Jika panjang array tidak seperti yang diharapkan, pengecualian harus dibuang.
Tunggu sebentar ...
genre .
Panjang <1 && genre. Panjang> 3 ? Karena tidak ada angka yang langsung jatuh ke dalam rentang nilai yang diperlukan oleh ekspresi (
[int.MinValue..1) dan
(3..int.MaxValue] ), hasil dari ekspresi akan selalu
salah . Oleh karena itu, pemeriksaan ini tidak melindungi terhadap apa pun, dan pengecualian yang diharapkan tidak akan dibuang.
Inilah yang persisnya
diingatkan oleh penganalisa:
V3022 Ekspresi 'genre. Panjang <1 && genre. Panjang> 3' selalu salah. Mungkin '||' Operator harus digunakan di sini. Fitur Evaluator.cs 242
Operasi fisi yang mencurigakan bertemu.
public static void CreateTrueThetaAndPhi(....) { .... double expectedRepeatOfTopicInDoc = averageDocLength / numUniqueTopicsPerDoc; .... int cnt = Poisson.Sample(expectedRepeatOfTopicInDoc); .... }
Peringatan PVS-Studio :
V3041 Ungkapan itu secara implisit dilemparkan dari tipe 'int' ke tipe 'double'. Pertimbangkan untuk menggunakan pemeran tipe eksplisit untuk menghindari hilangnya bagian fraksional. Contoh: double A = (double) (X) / Y; LDA Utilities.cs 74
Ini mencurigakan di sini: pembagian integer dilakukan (variabel
averageDocLength dan
numUniqueTopicsPerDoc bertipe
int ), dan hasilnya ditulis ke variabel bertipe
double . Pertanyaannya adalah: apakah ini dilakukan secara khusus, atau apakah pembagian bilangan real masih tersirat? Jika variabel
diharapkanRepeatOfTopicInDoc bertipe
int , ini akan menjernihkan kemungkinan pertanyaan.
Di tempat lain, metode
Poisson.Sample , argumen yang merupakan variabel mencurigakan yang
diharapkanRepeatOfTopicInDoc , digunakan, misalnya, seperti dijelaskan di bawah ini.
int numUniqueWordsPerTopic = Poisson.Sample((double)averageWordsPerTopic);
averageWordsPerTopic adalah tipe
int , yang sudah dikonversi menjadi
dua kali lipat di tempat penggunaan.
Dan berikut ini adalah tempat penggunaan lainnya:
double expectedRepeatOfWordInTopic = ((double)numDocs) * averageDocLength / numUniqueWordsPerTopic; .... int cnt = Poisson.Sample(expectedRepeatOfWordInTopic);
Harap perhatikan bahwa variabel memiliki nama yang sama seperti dalam contoh asli, hanya pembagian bilangan real yang digunakan untuk menginisialisasi
ekspektasiRepeatOfWordInTopic (karena konversi eksplisit
numDocs menjadi
dua kali lipat ).
Secara umum, ada baiknya melihat tempat awal di mana analis mengeluarkan peringatan.
Tetapi refleksi tentang apakah itu layak diedit dan bagaimana, biarkan penulis kode (mereka tahu lebih baik), tetapi mari kita melangkah lebih jauh. Ke divisi mencurigakan berikutnya.
public static NonconjugateGaussian BAverageLogarithm(....) { .... double v_opt = 2 / 3 * (Math.Log(mx * mz / Ex2 / 2) - m); if (v_opt != v) { .... } .... }
Peringatan PVS-Studio :
V3041 Ungkapan itu secara implisit dilemparkan dari tipe 'int' ke tipe 'double'. Pertimbangkan untuk menggunakan pemeran tipe eksplisit untuk menghindari hilangnya bagian fraksional. Contoh: double A = (double) (X) / Y; Runtime ProductExp.cs 137
Penganalisa lagi mendeteksi operasi mencurigakan dari divisi integer, seperti
2 dan
3 adalah bilangan literer bilangan bulat, dan hasil dari ekspresi 2/3 adalah
0 . Hasilnya, seluruh ekspresi berbentuk:
double v_opt = 0 * expr;
Setuju, sedikit aneh. Beberapa kali saya kembali ke peringatan ini, mencoba menemukan semacam tangkapan, dan tidak mencoba menambahkannya ke artikel. Metode ini dipenuhi dengan matematika dan berbagai formula (yang, sejujurnya, saya tidak benar-benar ingin membongkar), Anda tidak pernah tahu apa yang diharapkan. Selain itu, saya mencoba untuk menjadi skeptis mungkin tentang peringatan yang saya tulis di artikel, dan saya menggambarkannya hanya setelah mempelajarinya dengan lebih baik.
Tapi kemudian saya sadar - mengapa saya perlu faktor
0 , ditulis sebagai
2/3 ? Jadi tempat ini layak untuk dilihat.
public static void WriteAttribute(TextWriter writer, string name, object defaultValue, object value, Func<object, string> converter = null) { if ( defaultValue == null && value == null || value.Equals(defaultValue)) { return; } string stringValue = converter == null ? value.ToString() : converter(value); writer.Write($"{name}=\"{stringValue}\" "); }
Peringatan PVS-Studio :
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'nilai'. Kompiler WriteHelpers.cs 78
Penegasan analisis yang cukup adil berdasarkan kondisi. Referensi referensi nol dapat terjadi pada nilai ekspresi.
Setara ( nilai
default) jika
nilai == nol . Karena ungkapan ini adalah operan kanan operator | |, untuk menghitungnya, operan kiri harus
salah , dan untuk ini sudah cukup bahwa setidaknya salah satu variabel
nilai defaultValue \
nilai tidak
nol . Akibatnya, jika
defaultValue! = Null , dan
nilai == null :
- defaultValue == null -> false ;
- defaultValue == null && value == null -> false ; (pemeriksaan nilai tidak terjadi)
- value.Equals (defaultValue) -> NullReferenceException , karena nilainya nol .
Mari kita lihat kasus serupa:
public FeatureParameterDistribution( GaussianMatrix traitFeatureWeightDistribution, GaussianArray biasFeatureWeightDistribution) { Debug.Assert( (traitFeatureWeightDistribution == null && biasFeatureWeightDistribution == null) || traitFeatureWeightDistribution.All( w => w != null && w.Count == biasFeatureWeightDistribution.Count), "The provided distributions should be valid and consistent in the number of features."); .... }
Peringatan PVS-Studio :
V3080 Kemungkinan null dereference. Pertimbangkan untuk memeriksa 'traitFeatureWeightDistribution'. Fitur RekomendasiParameterDistribution.cs 65
Kami membuang kelebihan, hanya menyisakan logika untuk menghitung nilai Boolean, sehingga lebih mudah untuk mencari tahu:
(traitFeatureWeightDistribution == null && biasFeatureWeightDistribution == null) || traitFeatureWeightDistribution.All( w => w != null && w.Count == biasFeatureWeightDistribution.Count)
Sekali lagi, operan kanan || Ini dihitung hanya jika hasil perhitungan kiri
salah . Operan kiri bisa
salah , termasuk ketika
traitFeatureWeightDistribution == null dan
biasFeatureWeightDistribution! = Null . Maka operan yang tepat dari operator || akan dihitung, dan panggilan ke
traitFeatureWeightDistribution. Semua akan
menaikkan ArgumentNullException .
Sepotong kode menarik lainnya:
public static double GetQuantile(double probability, double[] quantiles) { .... int n = quantiles.Length; if (quantiles == null) throw new ArgumentNullException(nameof(quantiles)); if (n == 0) throw new ArgumentException("quantiles array is empty", nameof(quantiles)); .... }
PVS-Studio Warning :
V3095 Objek 'quantiles' digunakan sebelum diverifikasi dengan null. Periksa baris: 91, 92. Runtime OuterQuantiles.cs 91
Perhatikan bahwa properti
quantiles.Length pertama kali diakses , dan kemudian
quantiles diperiksa untuk
null . Akibatnya, jika
kuantil == nol , metode ini akan melempar pengecualian, hanya sedikit salah, dan sedikit tidak sesuai dengan yang dibutuhkan. Rupanya, mereka mengacaukan garis di beberapa tempat.
Jika Anda berhasil menangani deteksi kesalahan sebelumnya sendiri, saya sarankan Anda menyeduh secangkir kopi dan mencoba mengulanginya, menemukan kesalahan dalam metode di bawah ini. Untuk membuatnya sedikit lebih menarik, saya mengutip seluruh kode metode ini.
(
Tautan ke ukuran penuh )

Oke, oke, itu lelucon (atau apakah Anda berhasil?!). Mari sederhanakan tugasnya:
if (sample.Precision < 0) { precisionIsBetween = true; lowerBound = -1.0 / v; upperBound = -mean.Precision; } else if (sample.Precision < -mean.Precision) { precisionIsBetween = true; lowerBound = 0; upperBound = -mean.Precision; } else {
Apakah semakin baik? Penganalisis mengeluarkan peringatan berikut untuk kode ini:
V3008 Variabel 'lowerBound' ditugaskan nilai dua kali berturut-turut. Mungkin ini sebuah kesalahan. Periksa baris: 324, 323. Runtime GaussianOp.cs 324
Memang, di cabang yang
lain terakhir, nilai variabel
lowerBound ditugaskan dua kali berturut-turut. Rupanya (dan dilihat dari kode di atas), variabel
upperBound harus terlibat dalam salah satu tugas.
Kami mengikuti lebih jauh.
private void WriteAucMatrix(....) { .... for (int c = 0; c < classLabelCount; c++) { int labelWidth = labels[c].Length; columnWidths[c + 1] = labelWidth > MaxLabelWidth ? MaxLabelWidth : labelWidth; for (int r = 0; r < classLabelCount; r++) { int countWidth = MaxValueWidth; if (countWidth > columnWidths[c + 1]) { columnWidths[c + 1] = countWidth; } } .... }
Peringatan PVS-Studio :
V3081 Penghitung 'r' tidak digunakan di dalam loop bersarang. Pertimbangkan untuk memeriksa penggunaan penghitung 'c'. CommandLine ClassifierEvaluationModule.cs 459
Harap dicatat bahwa penghitung loop dalam -
r - tidak digunakan dalam tubuh loop ini. Karena itu, ternyata selama semua iterasi loop dalam operasi yang sama dilakukan pada elemen yang sama - setelah semua, indeks juga menggunakan counter loop luar (
c ), dan bukan dalam (
r ).
Mari kita lihat apa lagi yang menurut saya menarik.
public RegexpFormattingSettings( bool putOptionalInSquareBrackets, bool showAnyElementAsQuestionMark, bool ignoreElementDistributionDetails, int truncationLength, bool escapeCharacters, bool useLazyQuantifier) { this.PutOptionalInSquareBrackets = putOptionalInSquareBrackets; this.ShowAnyElementAsQuestionMark = showAnyElementAsQuestionMark; this.IgnoreElementDistributionDetails = ignoreElementDistributionDetails; this.TruncationLength = truncationLength; this.EscapeCharacters = escapeCharacters; }
Peringatan PVS-Studio :
Parameter konstruktor V3117 'useLazyQuantifier' tidak digunakan. Runtime RegexpFormattingSettings.cs 38
Konstruktor tidak menggunakan satu parameter -
useLazyQuantifier . Ini terlihat sangat mencurigakan dengan latar belakang fakta bahwa properti dengan nama dan tipe yang sesuai didefinisikan di kelas -
UseLazyQuantifier . Rupanya, mereka lupa menginisialisasi melalui parameter yang sesuai.
Bertemu dengan beberapa penangan acara yang berpotensi berbahaya. Contoh salah satunya diberikan di bawah ini:
public class RecommenderRun { .... public event EventHandler Started; .... public void Execute() {
Peringatan PVS-Studio :
V3083 Doa yang tidak aman dari acara 'Dimulai', NullReferenceException dimungkinkan. Pertimbangkan menugaskan acara ke variabel lokal sebelum menjalankannya. Evaluator RecommenderRun.cs 115
Faktanya adalah bahwa antara cek untuk ketidaksetaraan
nol dan panggilan dari pawang, suatu peristiwa dapat berhenti berlangganan, dan jika acara tersebut tidak memiliki pelanggan pada saat antara cek untuk
nol dan panggilan dari penangan,
NullReferenceException akan
dilemparkan . Untuk menghindari masalah seperti itu, misalnya, Anda dapat menyimpan tautan ke rantai delegasi dalam variabel lokal atau menggunakan operator '?.' untuk memanggil penangan.
Selain potongan kode di atas, ada 35 tempat seperti itu.
Omong-omong,
785 peringatan
V3024 juga dipenuhi. Peringatan
V3024 dikeluarkan saat membandingkan bilangan real menggunakan operator '! =' Atau '=='. Saya tidak akan membahas mengapa perbandingan seperti itu tidak selalu benar - lebih lanjut tentang ini ditulis dalam dokumentasi, ada juga tautan ke
StackOverflow (ini dia).
Mempertimbangkan bahwa formula dan perhitungan sering dipenuhi, peringatan ini juga dapat menjadi penting, meskipun mereka dibawa ke level 3 (karena mereka jauh dari relevan di semua proyek).
Jika Anda yakin bahwa peringatan ini tidak relevan, Anda dapat menghapusnya dengan
hampir satu klik , mengurangi total operasi penganalisa.
Kesimpulan
Entah bagaimana ternyata untuk waktu yang lama saya belum menulis artikel tentang memeriksa proyek, dan menyentuh proses ini lagi cukup menyenangkan. Saya harap Anda juga mempelajari sesuatu yang baru / bermanfaat dari artikel ini, atau setidaknya membacanya dengan penuh minat.
Saya berharap para pengembang melakukan koreksi awal terhadap area masalah dan saya mengingatkan Anda bahwa membuat kesalahan adalah hal yang normal, namun kami adalah orang-orang. Untuk ini, alat tambahan seperti analisa statis diperlukan untuk menemukan apa yang dilewatkan seseorang, bukan? Pokoknya - semoga sukses dengan proyek ini, dan terima kasih untuk pekerjaannya!
Dan ingatlah bahwa manfaat maksimum dari penganalisa statis dicapai dengan
penggunaan regulernya .
Semua yang terbaik!

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Sergey Vasiliev.
Apa Kesalahan Mengintai di Kode Infer.NET?