Elem,
Evo namestio sam jedan primer koji bi trebao ovo sto smo diskutovali da objasni. Prvo cu na brzinu objasni code, pa onda ceo primer, neka svako proba.
Code je napisan u C#, konzolna aplikacija, minimalno sam koristio .NET FW, cini mi se samo Console.WriteLine() i Console.ReadLine().
Postoje cetri testa: ReturnValueTest1, ReturnValueTest2, ExceptionTest1 i ExceptionTest2.
ReturnValueTest1 je test sa CreateBackup metodom koja ima gresku, programer je zaboravio da proveri gresku i return value ne vraca ispavne vrednosti. ReturnValueTest2 je isti test kao i prvi sa ispravljenom metodom.
ExceptionTest1 je test sa CreateBackup metodom koja ima gresku ali ova greska nece uticati na output u krajnjoj operaciji. ExcetionTest2 je isto sto i prvi test samo sa ispravljenom meodom.
Svi testovi se sastoje od sledece cetri metode Backup1, Backup2, CreateBackup, Compress i WriteFile:
Code:
private bool CreateBackup(string srcFileName, string destFileName)
{
// precodintions
if (srcFileName == null) return false;
if (destFileName == null) return false;
byte[] compressedBytes;
Compress(srcFileName, out compressedBytes);
WriteFile(destFileName, compressedBytes);
return true;
}
private bool Compress(string srcFileName, out byte[] compressedBytes)
{
compressedBytes = null;
// precodintions
if (srcFileName == null) return false;
Console.WriteLine("Compressing file : {0} ", srcFileName);
compressedBytes = new byte[] { 1, 2, 3, 4 };
return true;
}
public bool WriteFile(string destFileName, byte[] compressedBytes)
{
// precodintions
if (destFileName == null) return false;
if (compressedBytes == null) return false;
if (destFileName.EndsWith("log")) return false;
Console.WriteLine("Writing file : {0} ", destFileName);
return true;
}
public bool Backup1()
{
if (!CreateBackup(@"D:\t1.log", @"D:\b1.log")) return false;
if (!CreateBackup(@"D:\t2.txt", @"D:\b2.bkp")) return false;
if (!CreateBackup(@"D:\t3.txt", @"D:\b3.bkp")) return false;
return true;
}
public bool Backup2()
{
bool b1 = CreateBackup(@"D:\t1.log", @"D:\b1.log");
bool b2 = CreateBackup(@"D:\t2.txt", @"D:\b2.bkp");
bool b3 = CreateBackup(@"D:\t3.txt", @"D:\b3.bkp");
return b1 | b2 | b3;
}
Naime, ideja je da imamo neku backup batch operaciju koja treba da backup-uje vise fajlova, imamo dva tipa backup-a, jedan koji ukoliko dodje bilo gde do greske izlazi iz metode, i drugi koji prvo obavi sve operacije pa zatim akumulira rezultat svih operacija (ovo je isto promenljivo, da li se zeli AND ili OR operacija). U zavisnosti od testa sve metode se menjaju na odredjeni nacin, recimo return value vraca value kod loseg prosledjenog argumenta dok EH vraca exception.
Evo i celog testa, ostavio sam komentare tako da ne pisem ponovo sve ovde. Nadam se da ce biti pitanja, posto mnoge stvari sam samo pomenuo sto ce verovatno nekima biti nejasno, ali zato smo tu
Code:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
namespace ExceptionReturnValueTest
{
class Program
{
static void Main(string[] args)
{
ReturnValueTest1 rvt1 = new ReturnValueTest1();
Console.WriteLine("Testing return value with error");
Console.WriteLine("--------------------------------------------------------");
Console.ReadLine();
// u ovom slucaju Backup1 nas "lazno" informise
Console.WriteLine("Backup1 result {0}", rvt1.Backup1());
Console.WriteLine();
Console.WriteLine("Creating backup");
Console.WriteLine("Backup2 result : {0}", rvt1.Backup2());
Console.WriteLine();
ExceptionTest1 et = new ExceptionTest1();
Console.WriteLine("Testing exception with error");
Console.WriteLine("--------------------------------------------------------");
Console.ReadLine();
// u ovom slucaju Backup1 zbog EH nas obavestava da smo nesto propustili
Console.WriteLine("Backup1 result : {0}", et.Backup1());
Console.WriteLine();
Console.WriteLine("Creating backup");
Console.WriteLine("Backup2 result : {0}", et.Backup2());
Console.WriteLine();
ReturnValueTest2 rvt2 = new ReturnValueTest2();
Console.WriteLine("Testing return value with corrected method");
Console.WriteLine("--------------------------------------------------------");
Console.ReadLine();
Console.WriteLine("Creating backup");
Console.WriteLine("Backup1 result : {0}", rvt2.Backup1());
Console.WriteLine();
Console.WriteLine("Creating backup");
Console.WriteLine("Backup2 result : {0}", rvt2.Backup2());
Console.WriteLine();
ExceptionTest1 et2 = new ExceptionTest1();
Console.WriteLine("Testing exception with corrected method");
Console.WriteLine("--------------------------------------------------------");
Console.ReadLine();
Console.WriteLine("Backup1 result : {0}", et2.Backup1());
Console.WriteLine();
Console.WriteLine("Creating backup");
Console.WriteLine("Backup2 result : {0}", et2.Backup2());
Console.WriteLine();
Console.ReadLine();
}
}
// Prvi test je sa return value vrednosti, metoda CreateBackup nije dobro napisana
// zaboravlja se provera vrednosti WriteFile metode, ovo dovodi do nevalidnog stanja programa
public class ReturnValueTest1
{
private bool CreateBackup(string srcFileName, string destFileName)
{
// precodintions
if (srcFileName == null) return false;
if (destFileName == null) return false;
byte[] compressedBytes;
if (!Compress(srcFileName, out compressedBytes)) return false;
// ovde se zaboravlja provera return value, sto znaci da program odolazi u
// neko nedefinisano stanje, ovo moze da prouzrokuje bilo sta ali da kazemo
// da ce u ovom slucaju samo nastaviti sa ekzekucijom + side effect
WriteFile(destFileName, compressedBytes);
return true;
}
// Compress metoda je vise kao noise, da ne bi bas bilo obicno propagiranje
// CreateBackup-a WriteFile metodi
private bool Compress(string srcFileName, out byte[] compressedBytes)
{
compressedBytes = null;
// precodintions
if (srcFileName == null) return false;
Console.WriteLine("Compressing file : {0} ", srcFileName);
compressedBytes = new byte[] { 1, 2, 3, 4 };
return true;
}
public bool WriteFile(string destFileName, byte[] compressedBytes)
{
// precodintions
if (destFileName == null) return false;
if (compressedBytes == null) return false;
// ovo je dodato kao simulacija WriteFile methode, da bi kao na osnovu ulaza
// dobili neku false vrednost, recimo da bi u praksi ovo znacilo da je fajl
// system nedostupan (recimo write permission)
if (destFileName.EndsWith("log")) return false;
Console.WriteLine("Writing file : {0} ", destFileName);
return true;
}
// prvi slucaj : izlazi se iz metoda cim dodje do false vrednosti
public bool Backup1()
{
if (!CreateBackup(@"D:\t1.log", @"D:\b1.log")) return false;
if (!CreateBackup(@"D:\t2.txt", @"D:\b2.bkp")) return false;
if (!CreateBackup(@"D:\t3.txt", @"D:\b3.bkp")) return false;
return true;
}
// drugi slucaj : svaki rezultat se 'kesira' tek kada svi
// metodi zavrse posao proverava se ukupan rezultat
public bool Backup2()
{
bool b1 = CreateBackup(@"D:\t1.log", @"D:\b1.log");
bool b2 = CreateBackup(@"D:\t2.txt", @"D:\b2.bkp");
bool b3 = CreateBackup(@"D:\t3.txt", @"D:\b3.bkp");
return b1 | b2 | b3;
}
}
// Drugi test je ekvivalent sa EH,
// mala napomena:
// - nigde se ne hvata general catch osim u backup metodama, jer su one simulacija neke batch oepracije
// - provera argumenata se ne handluje preko exceptiona, tereba koristiti defensive programming*
public class ExceptionTest1
{
private void CreateBackup(string srcFileName, string destFileName)
{
// precodintions
if (srcFileName == null) throw new ArgumentException();
if (destFileName == null) throw new ArgumentException();
byte[] compressedBytes;
Compress(srcFileName, out compressedBytes);
// programer je zaboravio da handluje exception, ali ovde sada dolazi do sledeceg problema
// WriteFile baca IOException - ako programer uhvati exception sta dalje raditi, baciti novi exception
// jer se ne zna rezultat WriteFile operacije? Ja ovde vidim dva resenja:
// 1. Baciti novi exception, moze 'prevedeni' exception sa informacijama
// 2. Koristiti return value
// Prvo resenje znaci da koristimo Exception za komunikaciju, ali ovde opet treba gledati da li je exception
// nesto sto ocekujemo ili ne, jer ako ga ocekujemo onda treba koristiti drugo resenje - return value
WriteFile(destFileName, compressedBytes);
}
private void Compress(string srcFileName, out byte[] compressedBytes)
{
compressedBytes = null;
// precodintions
if (srcFileName == null) throw new ArgumentException();
Console.WriteLine("Compressing file : {0} ", srcFileName);
compressedBytes = new byte[] { 1, 2, 3, 4 };
}
public void WriteFile(string destFileName, byte[] compressedBytes)
{
// precodintions
if (destFileName == null) throw new ArgumentException();
if (compressedBytes == null) throw new ArgumentException();
// slicno kao kod return value, simulira write metodu
if (destFileName.EndsWith("log")) throw new IOException();
Console.WriteLine("Writing file : {0} ", destFileName);
}
public bool Backup1()
{
try
{
// ovo je interesantna stvar, po defaultu pozivi metoda sa exceptionima
// prekidaju ekzekuciju dok kod return value pozivi se nastavljaju (imamo podmuklu gresku)
// jos jedna stvar je ta sto mi imamo gresku u WriteFile metodi koju poziva CreateBackup
// posto smo zaboravili exception da handlujemo exception putuje niz stack i propagira
// se do ovog catch bloka
CreateBackup(@"D:\t1.log", @"D:\b1.log");
CreateBackup(@"D:\t2.txt", @"D:\b2.bkp");
CreateBackup(@"D:\t3.txt", @"D:\b3.bkp");
return true;
}
catch
{
return false;
}
}
public bool Backup2()
{
// a da bi vrednosti svakog poziva pojedinacno proverili, da bi se dobio isti efekat
// kao kod return value, onda je potrebno svaki poizv pojedinacno handlovati
bool b = false;
try { CreateBackup(@"D:\t1.log", @"D:\b1.log"); b = true; } catch { }
try { CreateBackup(@"D:\t2.txt", @"D:\b2.bkp"); b = true; } catch { }
try { CreateBackup(@"D:\t3.txt", @"D:\b3.bkp"); b = true; } catch { }
return b;
}
}
// Treci test je opet sa return value ali ovaj put CreateBackup metoda
// je dobro napisana sto rezultuje ispravnom output-u
public class ReturnValueTest2
{
private bool CreateBackup(string srcFileName, string destFileName)
{
// precodintions
if (srcFileName == null) return false;
if (destFileName == null) return false;
byte[] compressedBytes;
if (!Compress(srcFileName, out compressedBytes)) return false;
// programer je uvideo gresku i proverio return value za WriteFile
if (!WriteFile(destFileName, compressedBytes)) return false;
return true;
}
private bool Compress(string srcFileName, out byte[] compressedBytes)
{
compressedBytes = null;
// precodintions
if (srcFileName == null) return false;
Console.WriteLine("Compressing file : {0} ", srcFileName);
compressedBytes = new byte[] { 1, 2, 3, 4 };
return true;
}
public bool WriteFile(string destFileName, byte[] compressedBytes)
{
// precodintions
if (destFileName == null) return false;
if (compressedBytes == null) return false;
// ovo je dodato kao simulacija WriteFile methode, da bi kao na osnovu ulaza
// dobili neku false vrednost, recimo da bi u praksi ovo znacilo da je fajl
// system nedostupan (recimo write permission)
if (destFileName.EndsWith("log")) return false;
Console.WriteLine("Writing File : {0} ", destFileName);
return true;
}
public bool Backup1()
{
if (!CreateBackup(@"D:\t1.log", @"D:\b1.log")) return false;
if (!CreateBackup(@"D:\t2.txt", @"D:\b2.bkp")) return false;
if (!CreateBackup(@"D:\t3.txt", @"D:\b3.bkp")) return false;
return true;
}
public bool Backup2()
{
bool b1 = CreateBackup(@"D:\t1.log", @"D:\b1.log");
bool b2 = CreateBackup(@"D:\t2.txt", @"D:\b2.bkp");
bool b3 = CreateBackup(@"D:\t3.txt", @"D:\b3.bkp");
return b1 | b2 | b3;
}
}
// Cetvrti test je opet EH, ali ovaj put ispravljen propust, exception je handlovan
// ali rezultat je i dalje isti, batch prijavljuje identicnu sitaciju kao i kada je
// metod CreateBackup bio lose implementiran
public class ExceptionTest2
{
private void CreateBackup(string srcFileName, string destFileName)
{
// precodintions
if (srcFileName == null) throw new ArgumentException();
if (destFileName == null) throw new ArgumentException();
byte[] compressedBytes;
Compress(srcFileName, out compressedBytes);
// programer handluje exception, ali ovde sada dolazi do vec opisanog problema, sta raditi sa uhvacenim exceptionom?
// 1. Baciti novi exception, moze 'prevedeni' exception sa informacijama ili
// 2. Koristiti return value
// recimo da je ovo situacija koja ne moze da se desi, onda cemo da bacamo exception
// ako je sitacija koja moze da se desi onda vracamo return value
//
// uzecemo da je sitacija nemoguca pa cemo da bacamo dalje exception sto dodvodi do sledeceg pitanja
// da li jednostavno pustiti exception (posto je on vec tipa IOException) ili 'prevesti' u neki novi exception?
// (mala digresija, koji god metod da se izabere exception koji se baca mora biti izlistan u nekoj formi dokumentacije,
// kod jave postoji checked exceptions, kod C++ postoji exception specifications, kod C#-a izgleda nista osim XML dokumentacije)
//
try {
WriteFile(destFileName, compressedBytes);
}
catch (IOException)
{
throw new MyException();
}
}
private void Compress(string srcFileName, out byte[] compressedBytes)
{
compressedBytes = null;
// precodintions
if (srcFileName == null) throw new ArgumentException();
Console.WriteLine("Compressing file : {0} ", srcFileName);
compressedBytes = new byte[] { 1, 2, 3, 4 };
}
public void WriteFile(string destFileName, byte[] compressedBytes)
{
// precodintions
if (destFileName == null) throw new ArgumentException();
if (compressedBytes == null) throw new ArgumentException();
// slicno kao kod return value, simulira write metodu
if (destFileName.EndsWith("log")) throw new IOException();
Console.WriteLine("Writing file : {0} ", destFileName);
}
public bool Backup1()
{
try
{
CreateBackup(@"D:\t1.log", @"D:\b1.log");
CreateBackup(@"D:\t2.txt", @"D:\b2.bkp");
CreateBackup(@"D:\t3.txt", @"D:\b3.bkp");
return true;
}
catch
{
return false;
}
}
public bool Backup2()
{
bool b = false;
try { CreateBackup(@"D:\t1.log", @"D:\b1.log"); b = true; } catch { }
try { CreateBackup(@"D:\t2.txt", @"D:\b2.bkp"); b = true; } catch { }
try { CreateBackup(@"D:\t3.txt", @"D:\b3.bkp"); b = true; } catch { }
return b;
}
}
public class MyException : Exception { }
}