From checked Exception to code generation
Mi is ez? Nyavajgás és picsogás mennyire fárasztó a Java, ha már ismersz más koncepciókat is
Többféle vélemény van arról, hogy miként kell kivételeket (polgári néven Exception) kezelni. Java-ban két fő kivétel van:
- amit kezelni kell
- amit nem kell kezelni
Mikor azt írom nem kell kezelni nem azt jelenti, hogy hagyjuk figyelmen kívül. Csupán arról van szó, hogy Java-ban nem jól van kezelve. Pontosabban alap Java API nem kezeli jól.
Bárhová tévedsz checked exceptionökbe futsz bele. Nem tudod kikerülni. Még azokban az esetekben is, amikor API szinten lehetőséged van arra, hogy kivételkezelés nélkül is teljesen szabályos kódot tudj írni.
Iskolapélda a filekezelés. Alap, hogy file művelet előtt ellenőrzöd létezik-e, írható, olvasható-e (stb) a file. Ha mindent le tudsz ellenőrizni, akkor felteszem nem akarsz minden áron FileNotFoundException
-t kezelni. Teljesen elégedett lennék azzal, ha ez csak simán RuntimeException lenne. Jöjjön csak a nagy Exception, de kelljen minden áron lekezelnem. Főleg, ha nem is tudok vele mit kezdeni.
És itt a másik indok. A dobálódzó kivételek jelentős részét nem lehet értelmesen lekezelni. Azon kívül, hogy a felhasználót/programozót értesítem a lehetőségről, semmi mást nem tudok tenni. Semmit (az esetek 90%-ban)!
Nagyos sok és jó érvet lehet találni a neten. De nekem leginkább a Spring adja a referenciát. Mivel a Spring az egyik legjobban kitalált szoftvercsomag és nem kényszerít minden áron a kivételkezelésre, hanem hagyja, hogy az intelligenciád döntsön felőle, elegendő bizonyítékot kell szolgáltatnia bárkinek.
De azért vannak még vélemények:
- The Problem With Checked Exceptions
- " I would go as far as 98% of catch blocks are meaningless"
- Failure and Exceptions
- Does Java need Checked Exceptions?
- Java's checked exceptions were a mistake
Nem Springet használ az ember. Mindenkinek megvan a maga kedvenc utility library-je. De abban biztos vagyok, hogy Jakarta Commons család sokak kedvence. De általános betegsége, hogy nem foglalkozik a kivételekkel. Hanem simán továbbdelegálja a hívó programegységnek, azaz nekünk. És már kezdhetjük is írni a try-catch blokkokat és throws deklarációkat.
Az évek során rendszeres rutinommá vált, hogy használt commons utilokhoz wrapper osztályokat írok, amik csak delegálják a hívást és bármilyen exception is jön szépen becsomagolják egy RuntimeException-be. Végül is csak egyszer kell csinálni.
Egyszer? Sajnos nem. Céget és országot is váltottam. Több párhuzamos egymástól független projektben is részt vettem. És minden esetben újra kellett implementálnom ugyan azt. Nem nehéz, de már meguntam. Nosza generáljuk ki.
De nem olyan egyszerű. Van amiben az Eclipse elég sok segítséget tudott nyújtani, de még mindig macera volt.
Nosza írjunk egy scriptet, ami szépen elvégzi a generálást. Maga a generálás nem is nehéz. Ami gond az Java forrás feldolgozása. Kis kutakodással találtam pár parsert és a javaparser elég egyszerű volt ahhoz, hogy fel is használjam.
(Groovy)
Map convertables = ["eu.qualityontime.commons.io": [
/r:\environment\docs\commons-io-2.1\src\main\java\org\apache\commons\io\CopyUtils.java/,
//... additional files
],
"eu.qualityontime.commons.io.filefilter":[
/r:\environment\docs\commons-io-2.1\src\main\java\org\apache\commons\io\filefilter\FileFilterUtils.java/
]]
File targetDir = new File(/r:\TEMP\target/)
targetDir.mkdirs()
convertables.each{String packageName, List<String> files ->
File packageDir = new File(targetDir,packageName.replaceAll('\\.', "/"))
packageDir.mkdirs()
files.each{ filename ->
println filename
FileInputStream inp = openInputStream(new File(separatorsToSystem(filename)))
CompilationUnit cu = JavaParser.parse(inp)
StringBuilderWriter sbw = new StringBuilderWriter()
PrintWriter w = new PrintWriter(sbw)
StaticDelegateGeneratorVisitor v = new StaticDelegateGeneratorVisitor(packageName:packageName, print:w)
v.visit(cu, null)
println v.typeName
File outFile= new File(packageDir, v.typeName+".java")
outFile.createNewFile()
FileUtils.write(outFile, sbw.toString())
}
}
Maga a generátor sem túl bonyolult:
class StaticDelegateGeneratorVisitor extends VoidVisitorAdapter{
String packageName
PrintWriter print
String delegeName
PackageDeclaration importablePackage
String getTypeName(){
"QoT${delegeName}"
}
@Override
public void visit(CompilationUnit n, Object arg) {
print.println "package ${packageName};"
super.visit(n, arg);
}
@Override
public void visit(PackageDeclaration n, Object arg) {
importablePackage = n
super.visit(n, arg);
}
@Override
public void visit(ClassOrInterfaceDeclaration n, Object arg) {
delegeName = n.getName()
print.println "import ${importablePackage.getName()}.*;"
print.println "\npublic class ${getTypeName()}{"
super.visit(n, arg)
print.println "}"
}
@Override
public void visit(ImportDeclaration n, Object arg) {
print.print n
super.visit(n, arg)
}
@Override
public void visit(MethodDeclaration m, Object arg) {
if(!ModifierSet.isStatic(m.getModifiers())||!ModifierSet.isPublic(m.getModifiers())){
return
}
japa.parser.ast.type.Type type=m.getType()
List<Parameter> parameters = m.getParameters()==null?[]:m.getParameters()
List paramname = parameters.collect{it.id}
boolean voidReturn = type instanceof VoidType
print.println " public static ${type} ${m.getName()} (${parameters.join(', ')}){"
print.println " try{"
print.println " ${voidReturn?'':'return'} ${delegeName}.${m.getName()}(${paramname.join(', ')});"
print.println " }catch(Exception e){throw new RuntimeException(e);}"
print.println" }\n"
super.visit(m, arg)
}
}
Használd egészséggel!