Bagaimana kode aneh menyembunyikan kesalahan? Analisis TensorFlow.NET

TensorFlow.NET dan PVS-Studio

Analisis statis adalah alat yang sangat berguna untuk pengembang mana pun, karena membantu menemukan dalam waktu tidak hanya kesalahan, tetapi juga potongan kode yang mencurigakan dan aneh yang dapat menyebabkan kebingungan bagi programmer yang harus bekerja dengannya di masa depan. Gagasan ini akan ditunjukkan oleh analisis proyek C # TensorFlow.NET terbuka, yang sedang dikembangkan untuk bekerja dengan perpustakaan pembelajaran mesin TensorFlow yang populer.

Nama saya Nikita Lipilin. Beberapa waktu lalu saya bergabung dengan departemen programer C # di PVS-Studio. Secara tradisional, semua pendatang baru di tim menulis artikel yang membahas hasil pemeriksaan berbagai proyek terbuka menggunakan analisa statis PVS-Studio. Artikel semacam itu membantu karyawan baru untuk mengenal produk dengan lebih baik, dan pada saat yang sama memberikan manfaat tambahan dalam hal mempopulerkan metodologi analisis statis. Saya menyarankan agar Anda membiasakan diri dengan karya pertama saya pada topik analisis proyek terbuka.

pengantar


Berbagai kemungkinan kesalahan dalam kode program luar biasa. Beberapa dari mereka mengungkapkan diri mereka segera, setelah pemeriksaan permukaan aplikasi yang dibuat, yang lain mungkin sulit untuk diperhatikan bahkan ketika melakukan peninjauan kode oleh tim pengembang yang berpengalaman. Namun, itu juga terjadi bahwa, karena kurangnya perhatian atau alasan lain, programmer kadang-kadang menulis kode yang aneh dan tidak logis, yang, bagaimanapun, (tampaknya) berhasil memenuhi fungsinya. Tetapi hanya lebih jauh, ketika kembali ke apa yang ditulis, atau ketika orang lain mempelajari kode ini, pertanyaan mulai muncul yang tidak dapat dijawab.

Refactoring kode lama dapat berubah menjadi masalah, terutama jika bagian lain dari program bergantung padanya, jadi ketika Anda menemukan bahkan beberapa konstruksi yang bengkok secara terbuka, posisi ″ Apakah itu berfungsi? Jangan menyentuhnya! ″. Selanjutnya, ini membuatnya sulit untuk mempelajari sumbernya, dan karenanya, menjadi sulit untuk memperluas kemampuan yang tersedia. Basis kode tersumbat, ada kemungkinan bahwa beberapa masalah internal kecil dan tidak terlihat, tetapi berpotensi sangat tidak menyenangkan tidak akan diperbaiki dalam waktu.

Pada titik tertentu, konsekuensi dari kesalahan ini akan terasa, tetapi pencariannya akan memakan banyak waktu, karena kecurigaan pengembang akan jatuh pada sejumlah besar fragmen kode aneh yang tidak direaktor ulang pada satu waktu. Karena itu berbagai masalah dan keanehan dalam fragmen tertentu harus diperbaiki segera setelah penulisan. Dalam kasus ketika ada alasan yang masuk akal untuk meninggalkan segala sesuatu sebagaimana adanya (misalnya, jika kode ditulis sebagai semacam kosong ″ untuk nanti ″), fragmen seperti itu harus disertai dengan komentar penjelasan.

Perlu juga dicatat bahwa, terlepas dari kualifikasi pengembang, beberapa momen bermasalah dan tidak berhasil dapat lolos dari tatapannya, dan kadang-kadang "solusi sementara" dapat diterapkan di beberapa tempat, yang akan segera dilupakan dan menjadi "permanen". Selanjutnya, analisis kode tersebut (kemungkinan besar, pengembang lain akan terlibat dalam hal ini) akan mengambil banyak upaya yang tidak dapat diterima.

Dalam kasus seperti itu, tinjauan kode dapat membantu. Namun, jika tugasnya cukup serius, maka opsi ini akan membutuhkan banyak waktu. Selain itu, ketika ada banyak kesalahan kecil atau kekurangan, maka di belakang mereka pemeriksa mungkin tidak melihat kesalahan tingkat tinggi. Memeriksa kode menjadi rutin yang membosankan, dan lambat laun efektivitas tinjauan menurun.

Jelas, tugas-tugas rutin akan jauh lebih baik ditransfer dari orang ke komputer. Pendekatan ini digunakan di banyak bidang modernitas. Otomatisasi berbagai proses adalah kunci menuju kemakmuran. Apa otomasi dalam konteks topik ini?

Asisten yang dapat diandalkan dalam memecahkan masalah penulisan kode kerja yang masuk akal dan stabil adalah analisis statis. Setiap kali sebelum mengirimkan hasil kegiatan mereka ke ulasan, programmer akan dapat melakukan pemeriksaan otomatis dan tidak membebani pengembang lain (dan dirinya sendiri) dengan pekerjaan yang tidak perlu. Kode akan dikirim untuk verifikasi hanya setelah semua peringatan penganalisa telah diperhitungkan: kesalahan telah diperbaiki, dan momen aneh telah ditulis ulang, atau setidaknya dijelaskan oleh komentar.

Tentu saja, kebutuhan untuk tinjauan kode tidak hilang, tetapi analisis statis melengkapi dan sangat menyederhanakan implementasinya. Sebagian besar kesalahan akan diperbaiki berkat penganalisa, dan momen aneh pasti tidak akan dilupakan dan ditandai. Dengan demikian, dengan tinjauan kode akan dimungkinkan untuk fokus pada verifikasi kebenaran implementasi interaksi logis yang kompleks dan menemukan masalah mendasar yang, sayangnya, (sejauh ini) tidak dapat dideteksi oleh penganalisa.

TensorFlow.NET




Artikel ini terinspirasi oleh proyek TensorFlow.NET. Ini mewakili implementasi kemampuan untuk bekerja dengan fungsionalitas pustaka pembelajaran mesin TensorFlow yang populer melalui kode C # (omong-omong, kami juga mengujinya ). Gagasan ini sepertinya cukup menarik, karena pada saat penulisan, bekerja dengan perpustakaan hanya tersedia dalam Python, Java dan Go.

Kode sumber yang tersedia di GitHub terus diperbarui dan sekarang volumenya sedikit lebih dari seratus ribu baris. Setelah studi superfisial, ada keinginan untuk melakukan verifikasi menggunakan analisis statis. PVS-Studio digunakan sebagai alat khusus, yang telah membuktikan keefektifannya dalam sejumlah besar proyek yang berbeda .


Untuk TensorFlow.NET, penganalisa menampilkan sejumlah peringatan: 39 tingkat tinggi, 227 tingkat menengah dan 154 tingkat rendah (Anda dapat membaca tentang tingkat peringatan di sini di ayat "Tingkat peringatan dan set aturan diagnostik"). Analisis terperinci dari masing-masing dari mereka akan membuat artikel ini sangat besar, oleh karena itu hanya mereka yang tampaknya paling menarik bagi saya yang dijelaskan di bawah ini. Perlu juga dicatat bahwa beberapa masalah yang ditemukan ditemui beberapa kali dalam proyek, dan analisis masing-masing potongan kode berada di luar cakupan artikel ini.

Proyek itu sendiri menetapkan tugas yang agak serius, dan, sayangnya, penampilan berbagai jenis fragmen kode aneh tidak bisa dihindari. Pada artikel ini saya akan mencoba menunjukkan bahwa penggunaan analisis statis dapat sangat menyederhanakan pekerjaan pemrogram, menunjuk ke area yang dapat menimbulkan pertanyaan. Tidak selalu peringatan menunjukkan kesalahan, tetapi cukup sering itu menunjukkan kode yang akan menimbulkan pertanyaan pada seseorang. Dengan demikian, kemungkinan kode aneh didesain ulang atau, dalam hal apa pun, komentar yang sesuai meningkat secara signifikan.

Fragmen yang menarik perhatian saat mempelajari laporan penganalisa


Bahkan, sejumlah besar penganalisa peringatan untuk proyek ini bisa disebut bukan karena banyak kesalahan, tetapi kode yang aneh. Saat melihat garis yang mengeluarkan peringatan, setidaknya timbul kebingungan. Tentu saja, beberapa contoh yang disajikan di bawah ini mungkin solutions solusi sementara ″, tetapi mereka tidak memberikan komentar mengenai masalah ini, yang berarti bahwa seseorang yang akan bekerja dengan kode ini di masa mendatang akan memiliki pertanyaan, mencari jawaban yang akan memakan waktu ekstra.


Pada saat yang sama, beberapa peringatan menunjukkan kode yang jelas bukan hanya aneh, tetapi juga salah. Ini adalah bahaya utama ″ kode aneh ″ - sangat sulit untuk melihat kesalahan nyata jika di sana-sini Anda melihat solusi yang tidak dapat dipahami dan secara bertahap terbiasa dengan fakta bahwa kode tersebut tampaknya tidak benar.

Tur koleksi canggih


private static void _RemoveDefaultAttrs(....)
{
  var producer_op_dict = new Dictionary<string, OpDef>();
  producer_op_list.Op.Select(op =>
  {
    producer_op_dict[op.Name] = op;
    return op;
  }).ToArray();           
  ....
}

Peringatan Analyzer : V3010 Nilai kembali fungsi 'ToArray' harus digunakan. importir.cs 218

Penganalisis menganggap panggilan ke Toray pada saat ini mencurigakan, karena nilai yang dikembalikan oleh fungsi ini tidak ditugaskan ke variabel. Namun, kode semacam itu bukan kesalahan. Konstruk ini digunakan untuk mengisi kamus producer_op_dict dengan nilai-nilai yang sesuai dengan unsur-unsur dari daftar producer_op_list.Op . Panggilan ke ToArray diperlukan agar fungsi yang diteruskan sebagai argumen ke metode Pilih dipanggil untuk semua elemen dalam koleksi.

Menurut pendapat saya, kode tidak terlihat terbaik. Mengisi kamus agak tidak jelas, dan beberapa pengembang mungkin ingin menghapus panggilan ″ yang tidak perlu To dari ToArray . Akan jauh lebih sederhana dan lebih mudah untuk menggunakan foreach loop di sini :

var producer_op_dict = new Dictionary<string, OpDef>();

foreach (var op in producer_op_list.Op)
{
  producer_op_dict[op.Name] = op;
}

Dalam hal ini, kode terlihat sesederhana mungkin.

Momen serupa lainnya terlihat seperti ini:

public GraphDef convert_variables_to_constants(....)
{
  ....
  inference_graph.Node.Select(x => map_name_to_node[x.Name] = x).ToArray();
  ....
}

Peringatan Analyzer : V3010 Nilai kembali fungsi 'ToArray' harus digunakan. graph_util_impl.cs 48

Satu-satunya perbedaan adalah bahwa entri seperti itu terlihat lebih ringkas, tetapi hanya godaan untuk menghapus panggilan ToArray di sini tidak hilang, dan itu masih terlihat tidak jelas.

Solusi sementara


public GraphDef convert_variables_to_constants(....)
{
  ....
  var source_op_name = get_input_name(node);
  while(map_name_to_node[source_op_name].Op == "Identity")
  {
    throw new NotImplementedException);
    ....
  }
  ....
}

Peringatan Analyzer : V3020 'lemparan' tanpa syarat dalam satu lingkaran. graph_util_impl.cs 73

Dalam proyek yang sedang dipertimbangkan, pendekatan berikut sering digunakan: jika beberapa perilaku harus diimplementasikan kemudian, NotImplementedException dilemparkan ke tempat yang tepat . Jelas mengapa penganalisis memperingatkan kemungkinan kesalahan dalam bagian ini: menggunakan sementara alih-alih jika tidak benar-benar terlihat terlalu masuk akal.

Ini bukan satu-satunya peringatan yang muncul karena penggunaan solusi sementara. Misalnya, ada metode:

public static Tensor[] _SoftmaxCrossEntropyWithLogitsGrad(
  Operation op, Tensor[] grads
)
{
  var grad_loss = grads[0];
  var grad_grad = grads[1];
  var softmax_grad = op.outputs[1];
  var grad = _BroadcastMul(grad_loss, softmax_grad);

  var logits = op.inputs[0];
  if(grad_grad != null && !IsZero(grad_grad)) // <=
  {
    throw new NotImplementedException("_SoftmaxCrossEntropyWithLogitsGrad");
  }

  return new Tensor[] 
  {
    grad,
    _BroadcastMul(grad_loss, -nn_ops.log_softmax(logits))
  };
}

Peringatan Analyzer : Ekspresi V3022 'grad_grad! = Null &&! IsZero (grad_grad)' selalu salah. nn_grad.cs 93

Bahkan, NotImplementedException ("_ SoftmaxCrossEntropyWithLogitsGrad") tidak akan pernah dilempar, karena kode ini tidak dapat dijangkau. Untuk mengungkap alasannya, Anda harus membuka kode fungsi IsZero :

private static bool IsZero(Tensor g)
{
  if (new string[] { "ZerosLike", "Zeros" }.Contains(g.op.type))
    return true;

  throw new NotImplementedException("IsZero");
}

Metode ini mengembalikan true atau melempar pengecualian. Kode ini bukan kesalahan - jelas, implementasi di sini dibiarkan nanti. Yang terpenting adalah ″ lalu ″ benar-benar telah tiba. Nah, ternyata sangat berhasil bahwa PVS-Studio tidak akan membiarkan Anda lupa bahwa ada ketidaksempurnaan di sini :)

Apakah Tensor itu Tensor?


private static Tensor[] _ExtractInputShapes(Tensor[] inputs)
{
  var sizes = new Tensor[inputs.Length];
  bool fully_known = true;
  for(int i = 0; i < inputs.Length; i++)
  {
    var x = inputs[i];

    var input_shape = array_ops.shape(x);
    if (!(input_shape is Tensor) || input_shape.op.type != "Const")
    {
      fully_known = false;
      break;
    }

    sizes[i] = input_shape;
  }
  ....
}

Peringatan Analyzer : V3051 Pemeriksaan tipe berlebihan. Objek sudah dari tipe 'Tensor'. array_grad.cs 154

Jenis pengembalian metode bentuk adalah Tensor . Jadi input_shape adalah cek Tensor terlihat setidaknya aneh. Mungkin, setelah metode mengembalikan nilai dari jenis yang berbeda dan verifikasi masuk akal, tetapi juga mungkin bahwa alih-alih Tensor, syaratnya harus menentukan semacam pewaris kelas ini. Dengan satu atau lain cara, pengembang harus memperhatikan fragmen ini.

Pemeriksaan menyeluruh


public static Tensor[] _BaseFusedBatchNormGrad(....)
{
  ....
  if (data_format == "NCHW") // <=
    throw new NotImplementedException("");

  var results = grad_fun(new FusedBatchNormParams
  {
    YBackprop = grad_y,
    X = x,
    Scale = scale,
    ReserveSpace1 = pop_mean,
    ReserveSpace2 = pop_var,
    ReserveSpace3 = version == 2 ? op.outputs[5] : null,
    Epsilon = epsilon,
    DataFormat = data_format,
    IsTraining = is_training
  });

  var (dx, dscale, doffset) = (results[0], results[1], results[2]);
  if (data_format == "NCHW") // <=
    throw new NotImplementedException("");

  ....
}

Peringatan Analyzer :

  • V3021 Ada dua pernyataan 'jika' dengan ekspresi kondisional yang identik. Pernyataan 'jika' pertama berisi pengembalian metode. Ini berarti bahwa pernyataan kedua 'jika' tidak masuk akal nn_grad.cs 230
  • Ekspresi V3022 'data_format == "NCHW"' selalu salah. nn_grad.cs 247

Tidak seperti beberapa contoh sebelumnya, jelas ada yang salah dengan kode. Pemeriksaan kedua tidak masuk akal, karena jika kondisinya benar, maka pelaksanaan program tidak akan mencapainya sama sekali. Mungkin salah ketik diizinkan di sini, atau salah satu dari pemeriksaan umumnya berlebihan.

Ilusi pilihan


public Tensor Activate(Tensor x, string name = null)
{
  ....
  Tensor negative_part;
  if (Math.Abs(_threshold) > 0.000001f)
  {
    negative_part = gen_ops.relu(-x + _threshold);
  } else
  {
    negative_part = gen_ops.relu(-x + _threshold);
  }
  ....
}

Peringatan Analyzer : V3004 Pernyataan 'then' setara dengan pernyataan 'else'. gen_nn_ops.activations.cs 156 Demonstrasi yang

agak lucu tentang efektivitas menggunakan analisis statis dalam pengembangan. Sulit untuk datang dengan alasan yang masuk akal mengapa pengembang menulis kode khusus ini - kemungkinan besar, ini adalah kesalahan copy-paste yang khas (meskipun ini, tentu saja, mungkin lain ″ untuk nanti ″).

Ada fragmen lain dari rencana serupa, misalnya:

private static Operation _GroupControlDeps(
  string dev, Operation[] deps, string name = null
)
{
  return tf_with(ops.control_dependencies(deps), ctl =>
  {
    if (dev == null)
    {
      return gen_control_flow_ops.no_op(name);
    }
    else
    {
      return gen_control_flow_ops.no_op(name);
    }
  });
}

Peringatan Analyzer : V3004 Pernyataan 'then' setara dengan pernyataan 'else'. control_flow_ops.cs 135

Mungkin begitu pemeriksaan masuk akal, tetapi seiring waktu menghilang, atau beberapa perubahan lebih lanjut direncanakan di masa depan. Namun, tidak ada satu atau pun opsi lain yang cukup untuk meninggalkan sesuatu yang serupa dalam kode, tanpa menjelaskan keanehan ini dengan cara apa pun. Dengan tingkat probabilitas yang tinggi, kesalahan salin-tempel dibuat dengan cara yang persis sama.

Cek Terlambat


public static Tensor[] Input(int[] batch_shape = null,
  TF_DataType dtype = TF_DataType.DtInvalid,
  string name = null,
  bool sparse = false,
  Tensor tensor = null)
{
  var batch_size = batch_shape[0];
  var shape = batch_shape.Skip(1).ToArray(); // <=

  InputLayer input_layer = null;
  if (batch_shape != null)                   // <=
    ....
  else
    ....

  ....
}

Peringatan Analyzer : V3095 Objek 'batch_shape' digunakan sebelum diverifikasi dengan null. Periksa baris: 39, 42. keras.layers.cs 39

Kesalahan klasik dan agak berbahaya dari potensi penggunaan variabel, yang merupakan tautan ke mana-mana. Dalam hal ini, kode secara eksplisit menyiratkan kemungkinan bahwa batch_shape akan menjadi nol - ini jelas baik dari daftar argumen dan dari verifikasi selanjutnya dari variabel yang sama. Jadi, alat analisa di sini menunjukkan kesalahan yang jelas.

″ Lain untuk nanti ″?


public MnistDataSet(
  NDArray images, NDArray labels, Type dataType, bool reshape // <=
) 
{
  EpochsCompleted = 0;
  IndexInEpoch = 0;

  NumOfExamples = images.shape[0];

  images = images.reshape(
    images.shape[0], images.shape[1] * images.shape[2]
  );
  images = images.astype(dataType);
  // for debug np.multiply performance
  var sw = new Stopwatch();
  sw.Start();
  images = np.multiply(images, 1.0f / 255.0f);
  sw.Stop();
  Console.WriteLine($"{sw.ElapsedMilliseconds}ms");
  Data = images;

  labels = labels.astype(dataType);
  Labels = labels;
}

Peringatan Analyzer : V3117 Parameter konstruktor 'membentuk kembali' tidak digunakan. MnistDataSet.cs 15

Seperti beberapa keanehan lainnya, ini kemungkinan besar disebabkan oleh kenyataan bahwa fungsionalitasnya masih jauh dari implementasi sepenuhnya dan sangat mungkin bahwa parameter membentuk kembali akan digunakan entah bagaimana dalam konstruktor ini di masa depan. Tapi untuk saat ini, sepertinya dia dilemparkan ke sini begitu saja. Jika ini benar-benar dilakukan ″ untuk nanti ″, maka akan lebih baik untuk menandai ini dengan beberapa komentar. Jika tidak, maka ternyata kode yang membangun objek harus membuang parameter tambahan ke konstruktor, dan sangat mungkin bahwa akan lebih mudah untuk tidak melakukan ini.

Sulit dipahami kemungkinan nol dereferensi


public static Tensor[] _GatherV2Grad(Operation op, Tensor[] grads)
{
  ....
  if((int)axis_static == 0)
  {
    var params_tail_shape = params_shape.slice(new NumSharp.Slice(start:1));
    var values_shape = array_ops.concat(
      new[] { indices_size, params_tail_shape }, 0
    );
    var values = array_ops.reshape(grad, values_shape);
    indices = array_ops.reshape(indices, indices_size);
    return new Tensor[]
    {
      new IndexedSlices(values, indices, params_shape), // <=
      null,
      null
    };
  }
  ....
}

Peringatan Analyzer : V3146 Kemungkinan null dereference dari metode nilai 1 argumen pertama. '_Outputs.FirstOrDefault ()' dapat mengembalikan nilai nol default. array_grad.cs 199

Untuk memahami apa masalahnya, Anda harus terlebih dahulu beralih ke kode konstruktor indexedSlices :

public IndexedSlices(
  Tensor values, Tensor indices, Tensor dense_shape = null
)
{
  _values = values;
  _indices = indices;
  _dense_shape = dense_shape;

  _values.Tag = this; // <=
}

Jelas, meneruskan nol ke konstruktor ini akan menimbulkan pengecualian. Namun, mengapa penganalisa menganggap bahwa variabel nilai mungkin mengandung nol ?

PVS-Studio menggunakan teknik Analisis Aliran Data, yang memungkinkan Anda menemukan set nilai variabel yang mungkin ada di berbagai bagian kode. Peringatan memberi tahu Anda bahwa nol dalam variabel yang ditentukan dapat dikembalikan dengan baris berikut: _outputs.FirstOrDefault () . Namun, melihat kode di atas, Anda dapat menemukan bahwa nilai variabel nilai diperoleh dengan memanggil array_ops.reshape (grad, values_shape) . Jadi di manakah _outputs.FirstOrDefault () ?

Faktanya adalah bahwa ketika menganalisis aliran data, tidak hanya fungsi saat ini dipertimbangkan, tetapi juga semua disebut; PVS-Studio mendapatkan informasi tentang set nilai yang mungkin dari variabel mana pun di mana saja. Dengan demikian, peringatan berarti bahwa implementasi array_ops.reshape (grad, values_shape) berisi panggilan ke _outputs.FirstOrDefault () , yang hasilnya akhirnya dikembalikan.

Untuk memverifikasi ini, buka implementasi membentuk kembali :

public static Tensor reshape<T1, T2>(T1 tensor, T2 shape, string name = null)
            => gen_array_ops.reshape(tensor, shape, null);

Lalu pergi ke metode membentuk kembali yang disebut di dalam:
public static Tensor reshape<T1, T2>(T1 tensor, T2 shape, string name = null)
{
  var _op = _op_def_lib._apply_op_helper(
    "Reshape", name, new { tensor, shape }
  );
  return _op.output;
}

Fungsi _apply_op_helper mengembalikan objek dari kelas Operation yang berisi properti output . Setelah menerima nilainya, kode yang dijelaskan dalam peringatan itu disebut:

public Tensor output => _outputs.FirstOrDefault();

Tensor , tentu saja, adalah tipe referensi, jadi nilai default untuk itu adalah nol . Dari semua ini dapat dilihat bahwa PVS-Studio teliti menganalisis struktur logis dari kode, menembus jauh ke dalam struktur panggilan.

Penganalisa menyelesaikan pekerjaannya, menunjukkan tempat yang berpotensi bermasalah. Programmer harus memeriksa apakah suatu situasi dapat muncul ketika elemen-elemen dalam _output tidak ada.

Dengan demikian, analisis statis setidaknya akan memaksa pengembang untuk memperhatikan fragmen yang mencurigakan untuk mengevaluasi apakah kesalahan itu benar-benar terjadi di sana. Dengan pendekatan ini, jumlah kesalahan yang tidak diperhatikan akan berkurang dengan cepat.

Menunggu tidak dapat diandalkan?


private (LoopVar<TItem>, Tensor[]) _BuildLoop<TItem>(
  ....
) where ....
{
  ....
  // Finds the closest enclosing non-None control pivot.
  var outer_context = _outer_context;
  object control_pivot = null;
  while (outer_context != null && control_pivot == null) // <=
  {

  }

  if (control_pivot != null)
  {

  }
  ....
}

Peringatan Analyzer : V3032 Menunggu ungkapan ini tidak dapat diandalkan, karena kompiler dapat mengoptimalkan beberapa variabel. Gunakan variabel yang mudah menguap atau sinkronisasi primitif untuk menghindari hal ini. WhileContext.cs 212

Penganalisis menunjukkan bahwa implementasi menunggu seperti itu dapat dioptimalkan oleh kompiler, tapi saya ragu mereka benar-benar mencoba menerapkan menunggu di sini - kemungkinan besar, kode itu tidak ditambahkan dan akan dikembangkan lebih lanjut di masa depan. Mungkin layak untuk melempar NotImplementedException di sini , mengingat praktik ini digunakan di tempat lain dalam proyek. Dengan satu atau lain cara, saya percaya bahwa beberapa komentar jelas tidak akan sakit.

Melanggar batas


public TensorShape(int[][] dims)
{
  if(dims.Length == 1)
  {
    switch (dims[0].Length)
    {
      case 0: shape = new Shape(new int[0]); break;
      case 1: shape = Shape.Vector((int)dims[0][0]); break;
      case 2: shape = Shape.Matrix(dims[0][0], dims[1][2]); break; // <=
      default: shape = new Shape(dims[0]); break;
    }
  }
  else
  {
    throw new NotImplementedException("TensorShape int[][] dims");
  }
}

Peringatan Analyzer : V3106 Kemungkinan indeks di luar batas. Indeks '1' menunjuk di luar batas 'dims'. TensorShape.cs 107

Di antara potongan kode aneh yang saya lihat, ada kesalahan nyata, yang sangat sulit untuk dilihat. Fragmen berikut keliru di sini: redup [1] [2] . Mendapatkan elemen dengan indeks 1 dari array satu elemen jelas merupakan kesalahan. Pada saat yang sama, jika Anda mengubah fragmen menjadi meredup [0] [2] , kesalahan lain muncul - mendapatkan elemen dengan indeks 2 dari array dims [0] , panjangnya dalam cabang kasus ini adalah 2. Dengan demikian, masalah ini ternyata menjadi seakan dengan "double bottom".

Bagaimanapun, fragmen kode ini harus dipelajari dan diperbaiki oleh pengembang. Menurut pendapat saya, contoh ini adalah ilustrasi yang sangat baik tentang kinerja Analisis Aliran Data di PVS-Studio.

Olepatka?


private void _init_from_args(object initial_value = null, ....) // <=
{
  var init_from_fn = initial_value.GetType().Name == "Func`1"; // <=
  ....
  tf_with(...., scope =>
  {
    ....
    tf_with(...., delegate
    {
      initial_value = ops.convert_to_tensor(  // <=
        init_from_fn ? (initial_value as Func<Tensor>)():initial_value,
        name: "initial_value",
        dtype: dtype
      );
    });
    _shape = shape ?? (initial_value as Tensor).TensorShape;
    _initial_value = initial_value as Tensor; // <=
    ....
    _dtype = _initial_value.dtype.as_base_dtype(); // <=

    if (_in_graph_mode)
    {
      ....

      if (initial_value != null) // <=
      {
        ....
      }

      ....
    }

    ....
  });
}

Untuk memahami kode di atas, ada baiknya juga memperkenalkan implementasi fungsi tf_with:

[DebuggerStepThrough] // with "Just My Code" enabled this lets the 
[DebuggerNonUserCode()]  //debugger break at the origin of the exception
public static void tf_with<T>(
  T py, Action<T> action
) where T : ITensorFlowObject
{
  try
  {
    py.__enter__();
    action(py);
  }
  finally
  {
    py.__exit__();
    py.Dispose();
  }
}

Peringatan Analyzer : V3019 Mungkin variabel yang salah dibandingkan dengan nol setelah konversi jenis menggunakan kata kunci 'sebagai'. Periksa variabel 'initial_value', '_initial_value'. ResourceVariable.cs 137

_init_from_args adalah fungsi yang cukup banyak, sehingga banyak fragmen yang dihilangkan. Anda dapat melihatnya sepenuhnya dengan mengklik tautan . Meskipun peringatan pada awalnya tidak tampak serius bagi saya, setelah belajar, saya menyadari bahwa ada sesuatu yang salah dengan kode tersebut.

Pertama, harus dicatat bahwa metode ini dapat dipanggil tanpa melewati parameter dan secara default akan menjadi null di initial_value . Dalam hal ini, pengecualian akan diberikan pada baris pertama. Kedua, verifikasi

initial_value ke null ketimpangan terlihat aneh: jika initial_value benar-benar menjadi nol setelah memanggil ops.convert_to_tensor , maka _initial_value akan menjadi nol , yang berarti bahwa memanggil _initial_value.dtype.as_base_dtype () juga akan memberikan pengecualian.

Analyzer mengisyaratkan bahwa Anda mungkin perlu memeriksa nol adalah _initial_value , tetapi seperti yang disebutkan di atas, merujuk pada variabel ini terjadi sebelum tes ini, jadi opsi ini juga akan salah.

Apakah kesalahan kecil ini terlihat dalam fungsi raksasa tanpa PVS-Studio? Saya sangat meragukannya.

Kesimpulan


Dalam sebuah proyek dengan banyak contoh kode aneh, banyak masalah bisa disembunyikan. Si programmer, terbiasa melihat yang tidak bisa dipahami, berhenti memperhatikan kesalahan pada saat bersamaan. Konsekuensinya bisa sangat menyedihkan. Memang, di antara peringatan penganalisa ada juga yang salah, namun, dalam kebanyakan kasus, peringatan setidaknya menunjukkan fragmen kode yang dapat menyebabkan pertanyaan ketika dilihat oleh seseorang. Dalam kasus ketika kode aneh ditulis dengan sengaja, ada baiknya meninggalkan penjelasan sehingga fragmennya jelas bagi pengembang yang akan bekerja dengan kode ini di masa mendatang (bahkan jika itu berarti meninggalkan komentar untuk dirinya sendiri).

Pada saat yang sama, alat analisis statis, seperti PVS-Studio, dapat sangat membantu dalam menemukan potensi kesalahan dan keanehan sehingga dapat terlihat dan tidak dilupakan, dan semua solusi sementara selanjutnya disempurnakan dan diubah menjadi pekerjaan yang bersih, terstruktur, dan stabil. Kode.


Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Nikita Lipilin. Bagaimana kode aneh menyembunyikan kesalahan? Analisis TensorFlow.NET .

All Articles