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:

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:

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!

Mar 07, 2013
comments powered by Disqus

Links

Cool

RSS