İçeriğe geç
academia.sh

Ders 09 / 11

Kalite Nitelik Senaryoları

Kalite gereksiniminin sınanabilir yazılması: aynı on iki gereksinimin serbest cümle ve alanlı senaryo biçiminde yazılıp kaçının sınanabildiğinin sayılması, boş kalan alanların soruya çevrilmesi ve eşiği yazılmamış gereksinimin teslimde doğurduğu anlaşmazlığın ölçülmesi.

İçindekiler

Önceki dersler görünümleri ve diyagram türlerini soruya göre ayırdı: hangi resmin hangi soruyu yanıtladığı artık belli. Ama sonradan gelen birinin sorduğu soruların bir bölümü resimle yanıtlanamaz. “Bu sistem yeterince hızlı mı”, “bu şube kopukken ne olacak”, “bu yavaşlık kabul edilir mi” türünden sorular bir kutu-ok resmine bakılarak kapanmaz; bunların yanıtı bir sayıdır. Bu ders o sayının nereye yazıldığını ve yazılmadığında ne olduğunu ölçer.

Kalite niteliğinin ne olduğu ve nitelik senaryosunun hangi alanlardan oluştuğu Kalite ve Test Temelleri kursunda tanımlandı; burada o tanım tekrarlanmaz. Buradaki soru dardır: elde bir gereksinim listesi varken, o listenin kaç maddesi sınanabilir. Sınanabilirlik burada bir görüş değil bir ölçüttür — bir gereksinimden sınama yordamı yazılabiliyorsa sınanabilirdir, ve bir yordam için tetiklenecek bir uyaran, kurulacak bir ortam, gözlenecek bir yanıt ve karşılaştırılacak bir eşik gerekir. Hangi kalite niteliklerinin bir sistem için önemli olduğu ayrı bir konudur ve bu kursun sonraki kursunda ele alınır; burada nitelik listesi çıkarılmaz.

Ölçülecek Gereksinim Kümesi

Aşağıdaki blok on iki kalite gereksinimini iki biçimde modeller: serbest cümle ve alanlı senaryo. Alanlı biçim, serbest cümlenin söylediği kadarını taşır — cümlede olmayan hiçbir bilgi alana yazılmaz. Model diske de yazılır; sonraki iki blok aynı kümeyi okur.

// kume.mjs — gereksinim kumesini iki bicimde modeller ve sinanabilirligi olcer
import { mkdirSync, writeFileSync } from "node:fs";

// GR16: bolge kutuphane agindan toplanan on iki kalite gereksinimi. Alanli bicim,
// serbest cumlenin soyledigi kadarini tasir; hicbir alan uydurulmaz.
const GEREKSINIM = [
  ["Katalog aramasi hizli yanit vermeli.",
    "uye katalogda arama yapar", "", "sonuc listesi doner", "", ""],
  ["Sube baglantisi koptugunda odunc verme durmamali.",
    "sube ile merkez baglantisi kopar", "", "odunc verme surer", "", ""],
  ["Yogun saatte sistem ayakta kalmali.",
    "", "yogun saat", "sistem istek almayi surdurur", "", ""],
  ["Odunc kaydi kaybolmamali.",
    "", "", "kayit okunabilir kalir", "", ""],
  ["Gecikme bildirimi zamaninda gitmeli.",
    "odunc suresi dolar", "", "bildirim gonderilir", "", ""],
  ["Katalog toplu yuklemesi gece penceresine sigmali.",
    "toplu yukleme baslar", "gece bakim penceresi", "yukleme tamamlanir", "", ""],
  ["Uye kendi odunc gecmisini beklemeden gorebilmeli.",
    "uye gecmis sayfasini acar", "", "gecmis listelenir", "", ""],
  ["Odunc alma islemi, 500 eszamanli uye altinda isteklerin %95'inde 400 ms icinde tamamlanmali.",
    "uye odunc istegi gonderir", "500 eszamanli uye", "odunc kaydi olusur",
    "isteklerin %95'i 400 ms altinda", ""],
  ["Merkez veritabani dugumu kaybedildiginde odunc servisi 60 saniye icinde yedege gecmeli.",
    "birincil veritabani dugumu yanit vermez", "is saati yuku", "servis yedek dugumle calisir",
    "gecis 60 saniye icinde", ""],
  ["Yeni sube tanimi uretime tek dagitimla 1 saat icinde alinmali.",
    "sube tanimi eklenir", "tek dagitim penceresi", "sube uretimde gorunur",
    "eklemeden yayina 1 saat", "sube acilis takvimi: ayda iki sube"],
  ["Sistem yedeklenebilir olmali.",
    "", "", "", "", ""],
  ["Rapor ekrani makul surede acilmali.",
    "yonetici rapor ekranini acar", "", "rapor goruntulenir", "", ""],
];

const ALAN = ["uyaran", "ortam", "yanit", "olcu", "kaynak"];
const KUME = GEREKSINIM.map(([serbest, ...deger], i) => ({
  no: i + 1, serbest, alan: Object.fromEntries(ALAN.map((a, j) => [a, deger[j]])),
}));

mkdirSync("kume", { recursive: true });
writeFileSync("kume/kume.mjs",
  `export const ALAN = ${JSON.stringify(ALAN)};\n` +
  `export const KUME = ${JSON.stringify(KUME, null, 0)};\n`);

// Sinanabilirlik olcutu: bir sinama yordami yazilabilmesi icin tetiklenecek uyaran,
// kurulacak ortam, gozlenecek yanit ve karsilastirilacak esik gerekir. Esik, birim
// tasiyan bir sayidir; iki bicimde de ayni duzenli ifadeyle aranir.
const ESIK = /(\d+\s*(ms|saniye|dakika|saat|gun)|%\s*\d+|%\d+)/;
const KOSUL = /(koptugunda|kaybedildiginde|altinda|penceresi|penceresine|dagitimla|eszamanli|yogun|yuku)/;
const DORT = ["uyaran", "ortam", "yanit", "olcu"];

const serbestSinanabilir = (g) => ESIK.test(g.serbest) && KOSUL.test(g.serbest);
const alanliSinanabilir = (g) =>
  DORT.every((a) => g.alan[a] !== "") && ESIK.test(g.alan.olcu);

console.log(`${"no".padStart(2)}  ${"serbest bicim".padEnd(14)}${"alanli bicim (dolu alan)".padEnd(26)}bos alan`);
for (const g of KUME) {
  const dolu = DORT.filter((a) => g.alan[a] !== "");
  console.log(`${String(g.no).padStart(2)}  ` +
    `${(serbestSinanabilir(g) ? "sinanabilir" : "-").padEnd(14)}` +
    `${(dolu.join(",") || "-").padEnd(26)}` +
    `${4 - dolu.length}`);
}

const bosDort = KUME.reduce((s, g) => s + DORT.filter((a) => g.alan[a] === "").length, 0);
const bosBes = KUME.reduce((s, g) => s + ALAN.filter((a) => g.alan[a] === "").length, 0);
const yaziSerbest = KUME.reduce((s, g) => s + g.serbest.length, 0);
const yaziAlanli = KUME.reduce((s, g) =>
  s + ALAN.reduce((t, a) => t + a.length + 2 + g.alan[a].length, 0), 0);

console.log(`\nsinanabilir: serbest bicimde ${KUME.filter(serbestSinanabilir).length}/${KUME.length}, ` +
  `alanli bicimde ${KUME.filter(alanliSinanabilir).length}/${KUME.length}`);
console.log(`bos alan: dort alanda ${bosDort}, besinci alan (kaynak) dahil ${bosBes}`);
console.log(`yazim maliyeti (karakter): serbest ${yaziSerbest}, alanli ${yaziAlanli} ` +
  `(${(yaziAlanli / yaziSerbest).toFixed(2)} kat)`);
no  serbest bicim alanli bicim (dolu alan)  bos alan
 1  -             uyaran,yanit              2
 2  -             uyaran,yanit              2
 3  -             ortam,yanit               2
 4  -             yanit                     3
 5  -             uyaran,yanit              2
 6  -             uyaran,ortam,yanit        1
 7  -             uyaran,yanit              2
 8  sinanabilir   uyaran,ortam,yanit,olcu   0
 9  sinanabilir   uyaran,ortam,yanit,olcu   0
10  sinanabilir   uyaran,ortam,yanit,olcu   0
11  -             -                         4
12  -             uyaran,yanit              2

sinanabilir: serbest bicimde 3/12, alanli bicimde 3/12
bos alan: dort alanda 20, besinci alan (kaynak) dahil 31
yazim maliyeti (karakter): serbest 584, alanli 1079 (1.85 kat)

Alanlı Biçim Neyi Değiştirir

İki biçimde de sınanabilir gereksinim sayısı üçtür. Bu sonuç, alanlı senaryoya yüklenen en yaygın beklentiyi yıkar: alan açmak bilgi üretmez. Cümlede eşik yoksa alanda da yoktur; biçim değiştirmek bir gereksinimi sınanabilir kılmıyor.

Değişen şey başka: serbest biçim, sınanamayan dokuz gereksinim için tek bir bilgi veriyor — sayı yok. Alanlı biçim her biri için hangi alanın boş olduğunu veriyor. Dördüncü gereksinim üç alan eksik, on birincisi dört alanın dördünde de boş, altıncısında yalnız ölçü eksik. Bu, kusurun büyüklüğünü ve türünü ayıran bir ölçüdür; serbest cümlede ikisi de görünmez.

Boş alan sayısı yirmidir ve bu sayı doğrudan bir soru listesidir. Alanlı biçimin yazım maliyeti serbest biçimin 1,85 katı: 584 karaktere karşı 1.079 karakter. Bedel bu, karşılığı da şu — yirmi sorunun tek tek nereye sorulacağı belli.

Beşinci alan burada ayrı durur. kaynak, eşiğin nereden geldiğini tutar ve on iki gereksinimin yalnız birinde dolu. Sınanabilirlik ölçütü ilk dört alana bakar, çünkü sınama yordamı eşiğin kaynağını bilmeden de yazılabilir. Beşinci alanın neden ayrıca gerektiği bu dersin son bölümünde ölçülür.

Yazılmamış Eşiğin Bedeli

Sınanamayan bir gereksinim hemen bir bedel ödetmez. Bedeli teslim anında çıkar. Eşik yazılmadığı zaman ortadan kalkmaz; iki tarafın kafasında iki ayrı eşik olarak durur. İsteyen taraf “hızlı” derken bir sayı düşünür, geliştiren taraf başka bir sayı. Aşağıdaki blok bu durumu modelliyor: her gereksinime küçüğün iyi olduğu bir ölçek, iki tarafa birer eşik ve teslim edilen sisteme ölçülmüş bir değer veriliyor.

// anlasmazlik.mjs — kume.mjs'in yazdigi kume/kume.mjs'i okur; sinanamayan gereksinimin
// teslimde dogurdugu anlasmazligi olcer
import { KUME } from "./kume/kume.mjs";

// GR17: her gereksinimin kucugun iyi oldugu bir olcegi vardir; sinanamayan gereksinimde
// isteyen ile gelistiren yazili olmayan iki ayri esik tasir. Sinanabilir uc gereksinimde
// esik yazilidir, iki taraf da ayni sayiyi kullanir.
const ESIK = [                     // [olcek, isteyenin esigi, gelistirenin esigi, teslim araligi]
  ["arama yanit suresi (ms)", 300, 1200, [200, 1500]],
  ["odunc durma suresi (dk)", 5, 120, [0, 180]],
  ["reddedilen istek (binde)", 1, 20, [0, 40]],
  ["yillik kayip kayit", 0, 3, [0, 6]],
  ["bildirim gecikmesi (saat)", 1, 24, [0, 36]],
  ["toplu yukleme suresi (dk)", 60, 240, [30, 300]],
  ["gecmis sayfasi (ms)", 500, 2500, [300, 3000]],
  ["odunc islemi p95 (ms)", 400, 400, [250, 900]],
  ["yedege gecis (saniye)", 60, 60, [20, 180]],
  ["yayina alma (dakika)", 60, 60, [20, 240]],
  ["yedekten donus (saat)", 2, 24, [1, 48]],
  ["rapor ekrani (saniye)", 3, 20, [1, 30]],
];

// GR18: teslim olcumu, her gereksinim icin kendi araligindan duzgun dagilimla cekilir.
// Uretec kendi yazilmistir; tohum gorunurdur.
const uretec = (tohum) => () => {
  tohum = (tohum + 0x6d2b79f5) | 0;
  let t = Math.imul(tohum ^ (tohum >>> 15), 1 | tohum);
  t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
  return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
const TOHUM = 20802;
const TESLIM = 200;
const rastgele = uretec(TOHUM);

const sonuc = ESIK.map(() => ({ kabul: 0, anlasmazlik: 0, red: 0 }));
let teslimBasinaAnlasmazlik = 0;
for (let t = 0; t < TESLIM; t += 1) {
  let n = 0;
  ESIK.forEach(([, isteyen, gelistiren, [alt, ust]], i) => {
    const olculen = alt + rastgele() * (ust - alt);
    if (olculen <= Math.min(isteyen, gelistiren)) sonuc[i].kabul += 1;
    else if (olculen > Math.max(isteyen, gelistiren)) sonuc[i].red += 1;
    else { sonuc[i].anlasmazlik += 1; n += 1; }
  });
  teslimBasinaAnlasmazlik += n;
}

console.log(`tohum ${TOHUM}, ${TESLIM} teslim; her satir bir gereksinim (teslim sayisi)`);
console.log(`${"no".padStart(2)}  ${"olcek".padEnd(26)}${"esik".padStart(12)}` +
  `${"kabul".padStart(8)}${"anlasmazlik".padStart(13)}${"red".padStart(7)}`);
ESIK.forEach(([olcek, isteyen, gelistiren], i) => {
  console.log(`${String(i + 1).padStart(2)}  ${olcek.padEnd(26)}` +
    `${(isteyen === gelistiren ? `${isteyen}` : `${isteyen}/${gelistiren}`).padStart(12)}` +
    `${String(sonuc[i].kabul).padStart(8)}${String(sonuc[i].anlasmazlik).padStart(13)}` +
    `${String(sonuc[i].red).padStart(7)}`);
});

const yazili = ESIK.map(([, a, b], i) => (a === b ? i : -1)).filter((i) => i >= 0);
const yazisiz = ESIK.map((_, i) => i).filter((i) => !yazili.includes(i));
const topla = (dizi) => dizi.reduce((s, i) => s + sonuc[i].anlasmazlik, 0);
console.log(`\nesigi yazili ${yazili.length} gereksinim: ${topla(yazili)} anlasmazlik`);
console.log(`esigi yazilmamis ${yazisiz.length} gereksinim: ${topla(yazisiz)} anlasmazlik`);
console.log(`teslim basina ortalama anlasmazlik: ` +
  `${(teslimBasinaAnlasmazlik / TESLIM).toFixed(2)} (${KUME.length} gereksinimin uzerinden)`);
tohum 20802, 200 teslim; her satir bir gereksinim (teslim sayisi)
no  olcek                             esik   kabul  anlasmazlik    red
 1  arama yanit suresi (ms)       300/1200      12          142     46
 2  odunc durma suresi (dk)          5/120      10          112     78
 3  reddedilen istek (binde)          1/20       3           99     98
 4  yillik kayip kayit                 0/3       0           90    110
 5  bildirim gecikmesi (saat)         1/24       5          117     78
 6  toplu yukleme suresi (dk)       60/240      13          148     39
 7  gecmis sayfasi (ms)           500/2500       8          158     34
 8  odunc islemi p95 (ms)              400      45            0    155
 9  yedege gecis (saniye)               60      56            0    144
10  yayina alma (dakika)                60      35            0    165
11  yedekten donus (saat)             2/24       5           92    103
12  rapor ekrani (saniye)             3/20       9          104     87

esigi yazili 3 gereksinim: 0 anlasmazlik
esigi yazilmamis 9 gereksinim: 1062 anlasmazlik
teslim basina ortalama anlasmazlik: 5.31 (12 gereksinimin uzerinden)

Eşiği yazılı üç gereksinimde anlaşmazlık sıfırdır ve bu sıfır modelin yapısal sonucudur: tek eşik varsa iki taraf aynı ölçüme bakıp aynı sonuca varır. Sekizinci gereksinim iki yüz teslimin 155’inde reddedilmiş; bu bir başarısızlıktır ama tartışmasızdır. Red bir anlaşmazlık değil bir karardır.

Eşiği yazılmamış dokuz gereksinim iki yüz teslimde toplam 1.062 anlaşmazlık üretiyor — teslim başına 5,31. Yani her teslimde, on iki gereksinimin beşinden fazlası “sağlandı mı sağlanmadı mı” sorusuna iki farklı yanıt alıyor. En yüksek oran yedinci gereksinimde: iki yüz teslimin 158’i anlaşmazlıkla bitiyor. Nedeni ölçekte görünüyor — iki eşik arasındaki aralık (500 ile 2.500 ms) teslim aralığının büyük bölümünü kaplıyor. Anlaşmazlık olasılığı, iki tarafın örtük eşikleri arasındaki açıklıkla büyür. Cümle ne kadar belirsizse açıklık o kadar geniştir.

Bu, dersin maliyet karşılaştırmasını verir. Kümeyi alanlı biçimde yazmanın bedeli 495 fazla karakter ve yirmi sorunun sorulmasıdır; bu bedel bir kez ödenir. Sınanamayan bırakmanın bedeli teslim başına 5,31 anlaşmazlıktır ve her teslimde yeniden ödenir.

Soru Kümesi ve Beşinci Alan

Bu kursun ölçüsü, bir belgenin sonradan gelen birinin sorduğu soruların kaçını yanıtladığıdır. Kalite gereksinimlerine sorulan sorular beş tanedir ve her biri bir alana düşer.

// soru.mjs — kume.mjs'in yazdigi kume/kume.mjs'i okur; soru kumesini kumeye uygular
import { KUME } from "./kume/kume.mjs";

// Sonradan gelen birinin bir kalite gereksinimine sordugu bes soru; her soru bir alana duser.
const SORU = [
  ["Bu gereksinim ne zaman saglanmis sayilir?", "olcu"],
  ["Hangi kosulda olculecek?", "ortam"],
  ["Neyi tetikleyerek sinanacak?", "uyaran"],
  ["Sistemden ne gozlenecek?", "yanit"],
  ["Bu sayi nereden geldi?", "kaynak"],
];

console.log(`${"soru".padEnd(42)}${"yanitlanan".padStart(11)}${"yanitlanamayan".padStart(16)}`);
let yanitlanan = 0;
for (const [soru, alan] of SORU) {
  const d = KUME.filter((g) => g.alan[alan] !== "").length;
  yanitlanan += d;
  console.log(`${soru.padEnd(42)}${`${d}/${KUME.length}`.padStart(11)}` +
    `${String(KUME.length - d).padStart(16)}`);
}
const toplam = SORU.length * KUME.length;
console.log(`toplam ${toplam} soru: ${yanitlanan} yanitlanan, ${toplam - yanitlanan} yanitlanamayan`);

// Bir gereksinimi sinanabilir kilmak icin sorulacak soru sayisi (besinci alan disarida:
// sinanabilirlik dort alana bakar).
const DORT = ["olcu", "ortam", "uyaran", "yanit"];
const eksik = KUME.map((g) => [g.no, DORT.filter((a) => g.alan[a] === "").length,
  g.serbest.slice(0, 34)]).sort((a, b) => a[1] - b[1] || a[0] - b[0]);
console.log(`\n${"no".padStart(2)}  ${"sorulacak soru".padStart(14)}  gereksinim`);
for (const [no, n, c] of eksik) console.log(`${String(no).padStart(2)}  ${String(n).padStart(14)}  ${c}`);
console.log(`kumeyi sinanabilir kilmak: ${eksik.reduce((s, [, n]) => s + n, 0)} soru; ` +
  `tek soruyla kapanan ${eksik.filter(([, n]) => n === 1).length} gereksinim, ` +
  `uc ya da daha fazla soru gereken ${eksik.filter(([, n]) => n >= 3).length} gereksinim`);
soru                                       yanitlanan  yanitlanamayan
Bu gereksinim ne zaman saglanmis sayilir?        3/12               9
Hangi kosulda olculecek?                         5/12               7
Neyi tetikleyerek sinanacak?                     9/12               3
Sistemden ne gozlenecek?                        11/12               1
Bu sayi nereden geldi?                           1/12              11
toplam 60 soru: 29 yanitlanan, 31 yanitlanamayan

no  sorulacak soru  gereksinim
 8               0  Odunc alma islemi, 500 eszamanli u
 9               0  Merkez veritabani dugumu kaybedild
10               0  Yeni sube tanimi uretime tek dagit
 6               1  Katalog toplu yuklemesi gece pence
 1               2  Katalog aramasi hizli yanit vermel
 2               2  Sube baglantisi koptugunda odunc v
 3               2  Yogun saatte sistem ayakta kalmali
 5               2  Gecikme bildirimi zamaninda gitmel
 7               2  Uye kendi odunc gecmisini beklemed
12               2  Rapor ekrani makul surede acilmali
 4               3  Odunc kaydi kaybolmamali.
11               4  Sistem yedeklenebilir olmali.
kumeyi sinanabilir kilmak: 20 soru; tek soruyla kapanan 1 gereksinim, uc ya da daha fazla soru gereken 2 gereksinim

Altmış sorunun 29’u yanıtlanıyor, 31’i yanıtlanamıyor. Soruların yanıtlanma oranı düzenli biçimde düşüyor: sistemden ne gözleneceği on iki gereksinimin on birinde belli, neyin tetikleneceği dokuzunda, hangi koşulda ölçüleceği beşinde, ne zaman sağlanmış sayılacağı yalnız üçünde. Yani gereksinim yazan kişi ne istediğini anlatabiliyor, ne zaman yeteceğini anlatamıyor.

En düşük satır beşinci alandır: eşiğin kaynağı on iki gereksinimin yalnız birinde yazılı. Eşiği yazılı üç gereksinimden ikisinde bile sayının nereden geldiği kayıtlı değil. Bu ayrım küçük görünür ama sonucu ağırdır: 400 ms eşiği sınanabilir bir eşiktir, ancak altı ay sonra “bu eşik yükseltilebilir mi” sorusu geldiğinde yanıtlanamaz. Kaynağı yazılı olan tek gereksinimde (şube açılış takvimi, ayda iki şube) bu soru yanıtlanır: takvim seyrekleşirse eşik gevşetilebilir. Ölçü, gereksinimin sınanmasını sağlar; eşiğin kaynağı, gereksinimin değiştirilmesini sağlar.

Son tablo yirmi sorunun nasıl dağıldığını gösterir: üçü zaten sınanabilir, biri tek soruyla kapanıyor, altısı ikişer soru istiyor, ikisi üç ve dört soru istiyor. On birinci gereksinim aslında bir gereksinim değil bir başlıktır; dört alanının dördü de boş olduğu için tek soruyla değil ayrı bir çalışmayla doldurulur.

Özet

  • Sınanabilirlik bir ölçüttür: bir gereksinimden sınama yordamı yazılabiliyorsa sınanabilirdir; yordam için uyaran, ortam, yanıt ve birim taşıyan bir eşik gerekir.
  • Aynı on iki gereksinim iki biçimde de 3/12 sınanabilir çıkıyor — alanlı biçim bilgi üretmez, eksik bilgiyi görünür kılar: 20 boş alan, yani 20 tek tek sorulabilir soru.
  • Alanlı biçimin yazım maliyeti 1,85 kat (584’e karşı 1.079 karakter) ve bir kez ödenir; eşiği yazılmamış dokuz gereksinim 200 teslimde 1.062 anlaşmazlık, teslim başına 5,31 anlaşmazlık üretiyor.
  • Anlaşmazlık olasılığı iki tarafın örtük eşikleri arasındaki açıklıkla büyür; eşiği yazılı üç gereksinimde anlaşmazlık sıfırdır, red bile tartışmasızdır.
  • Beş sorunun 60 uygulamasından 31’i yanıtsız kalıyor; en yanıtsız soru “bu sayı nereden geldi” (11/12 yanıtsız) — ölçü sınamayı, eşiğin kaynağı değiştirmeyi mümkün kılar.

Sonraki Adım

Gereksinim kümesi ölçülebilir hale getirildiğinde ortaya bir belge çıkar: alanları dolu senaryolar, eşikler, kaynaklar. Bu belge yazıldığı gün kodla uyumludur. Sonraki ders o günden sonrasını ölçer. Kod değişmeye devam eder, belge yerinde durur ve aradaki fark büyür. Bu fark tahmin edilerek değil sayılarak bulunur: bir modül grafiğinden çıkarılan gerçek ile belgedeki iddia karşılaştırılır, bir dizi değişiklik uygulandıkça sapmanın nasıl arttığı izlenir ve iki güncel tutma düzeni — elle güncelleme ile belgeden üretilen denetim — yakaladıkları sapma, ödettikleri bakım maliyeti ve ürettikleri yanlış alarm üzerinden karşılaştırılır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat