Kontinuierliches Qualitäts-Controlling langlebiger Softwaresysteme Dr. Florian Deißenböck msg systems ag 15. März 2013 Continuous Quality in Software Engineering
Agenda Software-Wartung»Schlechter Code«Qualitätsverfall Qualitäts-Controlling Werkzeugunterstützung
Anforderungen: Geschäftsprozesse, Fahrzeugfunktionen, Software-Wartung Software-System Technologie: Hardware, BS, Sprachen, Datenbanken,
Relevanz Lebenszyklus-Kosten 100% 80% 60% 40% 20% Hardware Development Maintenance Wartungsfälle 60% 50% 40% 30% 20% 10% 0% 1970 1985 2000 0% Corrective Adaptive Perfective
Fehleinschätzungen Wartung ist reaktiv, unkreativ Wartung erfordert keine spezielle Qualifikation Wartungs-Schulungen 80% 70% 60% 50% 40% 30% 20% 10% Prüfung der Wartbarkeit 80% 70% 60% 50% 40% 30% 20% 10% 0% ja nein Katrin Katheder, Studie zur Software-Wartung, Bachelor Thesis, TU München, 2003 0% ja nein
Einflussfaktoren H. Sneed, Estimating the Costs of Software Maintenance Tasks, ICSM, 1995
#include <math.h> #include <sys/time.h> #include <X11/Xlib.h> #include <X11/keysym.h> double L,o,P,_=dt,T,Z,D=1,d, s[999],e,h= 8,I, J,K,w[999],M,m,O,n[999],j=33e-3,i= 1E3,r,t, u,v,w,s= 74.5,l=221,X=7.26, a,b,a=32.2,c, F,H; int N,q, C, y,p,u; Window z; char f[52] ; GC k; main(){ Display*e= XOpenDisplay( 0); z=rootwindow(e,0); for (XSetForeground(e,k=XCreateGC (e,z,0,0),blackpixel(e,0)) ; scanf("%lf%lf%lf",y +n,w+y, y+s)+1; y ++); XSelectInput(e,z= XCreateSimpleWindow(e,z,0,0,400,400, 0,0,WhitePixel(e,0) ),KeyPressMask); for(xmapwindow(e,z); ; T=sin(O)){ struct timeval G={ 0,dt*1e6} ; K= cos(j); N=1e4; M+= H*_; Z=D*K; F+=_*P; r=e*k; W=cos( O); m=k*w; H=K*T; O+=D*_*F/ K+d/K*E*_; B= sin(j); a=b*t*d-e*w; XClearWindow(e,z); t=t*e+ D*B*W; j+=d*_*d-_*f*e; P=W*E*B-T*D; for (o+=(i=d*w+e *T*B,E*d/K *B+v+B/K*F*D)*_; p<y; ){ T=p[s]+i; E=c-p[w]; D=n[p]-L; K=D*m-B*T-H*E; if(p [n]+w[ p]+p[s ]== 0 K <fabs(w=t*r-i*e +D*P) fabs(d=t *D+Z *T-a *E)> K)N=1e4; else{ q=w/k *4E2+2e2; C= 2E2+4e2/ K *D; N-1E4&& XDrawLine(e,z,k,N,U,q,C); N=q; U=C; } ++p; } L+=_* (X*t +P*M+m*l); T=X*X+ l*l+m *M; XDrawString(e,z,k,20,380,f,17); D=v/l*15; i+=(b *l-m*r -X*Z)*_; for(; XPending(e); u *=CS!=N){ XEvent z; XNextEvent(e,&z); ++*((N=XLookupKeysym (&z.xkey,0))-it? N-LT? UP-N?& E:& J:& u: &h); --*( DN -N? N-DT?N== RT?&u: & W:&h:&J ); } m=15*f/l; c+=(i=m/ l,l*h +I*M+a*X)*_; H =A*r+v*X-F*l+( E=.1+X*4.9/l,t =T*m/32-I*T/24 )/S; K=F*M+( h* 1e4/l-(T+ E*5*T*E)/3e2 )/S-X*d-B*A; a=2.63 /l*d; X+=( d*l-t/s *(.19*E +a *.64+J/1e3 )-M* v +A* Z)*_; l += K *_; W=d; sprintf(f, "%5d %3d" "%7d",p =l /1.7,(C=9E3+ O*57.3)%0550,(int)i); d+=t*(.45-14/l* X-a*130-J*.14)*_/125e2+F*_*v; P=(T*(47 *I-m* 52+E*94 *D-t*.38+u*.21*E) /1e2+W* 179*v)/2312; select(p=0,0,0,0,&g); v-=( W*F-T*(.63*m-I*.086+m*E*19-D*25-.11*u )/107e2)*_; D=cos(o); E=sin(o); } }
} for (const_iterator<permission> permission = permissions.begin(); permission!= permissions.end(); ++permission) { if (!permission->isinternal()) { if (user->haspermission (permission)) { cout << " " << permission << endl; } } } } } } /* bezieht sich auf if in Zeile 172 */ 30.444 Zeilen C# in einer Klasse 3.526 Zeilen C# in einer Methode Schachtelungstiefe 24 14 Parameter
Bezeichner Anzahl S0 810 S1 612 r1 490 I1 423 S2 362 i1 248 S3 211 SQL 199 CM 193 I2 155 Type # % chars % Keywords 967.665 11% 4.650.273 13% Delimiters 4.096.112 47% 4.096.112 11% Operators 531.444 6% 669.932 2 % Identifiers 2.873.232 32% 25.646.263 72% Literals 301.081 3% 708.308 2% Total 8.769.534 100% 35.770.888 100% Eclipse 3.0: 94.829 Bezeichner bestehend aus 7.233 Wörtern Identifikatoren sind das Vokabular eines Programms (ca. 70%) Synonyme & Homonyme Limitierung durch Programmiersprachen F. Deißenböck, M. Pizka, Concise and Consistent Naming, IWPC, 2005
/* if-else-kaskade */ if (GlobalSettings::mayPrintToConsole ()) { if (!user->isadmin()) { if (!permission->isinternal()) { if (user->haspermission...... int search (String s, String[] strings) { //strings = tolowercase(strings); strings = sort(strings); List<String> l = aslist(strings); return search(s, strings); }
TODO: ist das wirklich Okay, einem QWidget einen QObject Parent zu geben? TODO: Soll das so sein? Jetzt echt? Alle alten Sachen in die Tonne? TODO: WEG MIT DIESEM DRECK - RK SOLLTE FÜR JEDEN INDEX IMMER TODO: Nicht benutzen, hat hier überhaupt nix verloren FIXME: solve timeout problem FIXME: REFACTOREN! ich habe das jetzt einfache rüberkopiert, ist Müll so TODO: remove this hack TODO: Worin unterscheidet sich dieser Test vom vorherigen?? CQSE GmbH 2013
public double calc (double d1, double 2) { if (d1 == d2) { return 0; }... public double comp (String s1, String s2) { if (s1 == s2) { return 0; }... if (condition); { code_in_if (); } if (a<1 && a>5) { code_in_if (); } Socket s = new Socket ("192.168.0.5", 8080); return valid? true : false; Redundante Literale Magic Numbers GOTO Ausgerollte Schleifen public int find (String[] strings) { prepare(strings); int result = findprep(strings); Logger.debug(sort(strings)); return result; }
private static class Dir extends PathEntry { private String dir; Dir(String d) { dir = d; } ClassFile getclassfile(string name, String suffix) throws IOException { final File file = new File(dir + File.separatorChar + name.replace('.', File.separatorChar) + suffix); return file.exists()? new ClassFile() { public InputStream getinputstream() throws IOException { return new FileInputStream(file); } public String getpath() { try { return file.getcanonicalpath(); } catch(ioexception e) { return null; } } public long gettime() { return file.lastmodified(); } public long getsize() { return file.length(); } public String getbase() { return dir; } } : null; }...
try {... } catch (Throwable t) {} try {... } catch (Exception ex) { ex.printstracktrace(); } try {... } catch (Exception ex) { throw new RuntimeException(ex); } ungenügende/keine Behandlung von Ausnahmen inkonsistente Verwendung von Ausnahmen Ausnahmen vs Rückgabewerte Java: Checked vs Unchecked Exceptions
Juergens et al., Do Code Clones Matter?, ICSE 2009
Studie Identifikation von über 100 Fehlern in produktiver Software 17 44 46 Kritisch Nutzersichtbar Nicht nutzersichtbar Juergens et al., Do Code Clones Matter?, ICSE 2009
DEMO CQSE GmbH 2013
>10 Programmiersprachen in einem System Wahl der Sprache auf Basis der Entwicklerverfügbarkeit veraltete Technologien Sprachvermischung
<body> <% If Request.QueryString( ID ) <> Then %> <SCRIPT language= JavaScript > function dologin() { X } </script> <% Else %> <SCRIPT language= JavaScript > function dologin() { Y } </script> <% End If %>... </body> CQSE GmbH 2013
35 Trigger entführte Tabellen/verlorene User SQL-Injection Impact-Analyse auf Zuruf
Tests, die kontinuierlich fehlschlagen Unit-Tests, die nichts testen Mangelnde Testbarkeit Capture&Replay-Werkzeuge
JUnit Samples Core TextUI Runner Extensions Framework Experimental Tests Lib Matchers Hamcrest
Studie Projekt Alter kloc #Beziehungen #Abweichungen Doku. Probleme Architektur- Verletzungen A 4 454 8.254 994 ~90% ~10% B 2 495 5.388 1039 ~88% ~12% C 6 317 4.385 376 ~72% ~28% Erkenntnisse Dokumentation typischerweise veraltet Vielzahl relevanter Verletzungen aufgedeckt (inkl. Bugs) gemeinsames Architekturverständnis ist nötig M. Feilkas et al., The Loss of Architectural Knowledge during System Evolution, ICPC 2009
DEMO CQSE GmbH 2013
Anforderungen: Geschäftsprozesse, Fahrzeugfunktionen, Software-Wartung Software-System Technologie: Hardware, BS, Sprachen, Datenbanken,
»Programs, like people, get old«*»software aging in all successful products«ursachen: mangelnde Bewegung unkundige Behandlung Symptome: Gewichtszunahme sinkende Performance und Zuverlässigkeit Prävention: Vorausplanen (Qualität, Flexibles Design) Aufzeichnung aufbewahren (Dokumentation) zweite Meinung einholen (Review) Heilkunde: Alterungsprozess aufhalten, Nachdokumentation Restrukturieren, Amputation * D. L. Parnas, Software Aging, ICSE, 1994
Beispiel Bell Labs: 100Mloc, 5.000 Module, 15 Jahre, 10.000 Entwickler 1988 1989 1996 S. G. Eick et al., Does Code Decay?, IEEE Trans. Softw. Eng., 2001 Phänomen: Änderungsaufwand/Fehlerpotential zunehmend Symptome:»bloated«Code, Behelfslösungen, Ursachen: Zeitdruck, Werkzeuge, Umfeld, Team, Architektur, Proportional zur Anzahl Änderungen
350.000 LoC Clone LoC 35.000 300.000 30.000 250.000 25.000 200.000 20.000 150.000 15.000 100.000 10.000 50.000 5.000 0 0 Aug. 04 Okt. 04 Dez. 04 Feb. 05 Apr. 05 Jun. 05 Aug. 05 Okt. 05 Dez. 05 Feb. 06 Apr. 06 Jun. 06 Aug. 06 Okt. 06 Dez. 06
CQSE GmbH 2013
Anforderungen: Geschäftsprozesse, Fahrzeugfunktionen, Software-Wartung Software-System Kontinuierliches Qualitäts-Controlling Technologie: Hardware, BS, Sprachen, Datenbanken,
Änderungen gewünschte Wartbarkeit Abweichung Korrekturen Wartbarkeit Q-Ing. Entwickler System Analyse-Ergebnis (feedback) Qualitäts-Analyse Review Analyse-Tool
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
Trugschluss: Messbar == Relevant CQSE GmbH 2013
org.junit.runners.enclosed org.junit.experimental.theories.theories org.junit.internal.runners.testclassrunne r org.junit.tests.parameterizedtestmethodtest org.junit.tests.alltests org.junit.tests.testmethodtest org.junit.internal.requests.classrequest org.junit.tests.enclosedtest org.junit.tests.singlemethodtes t org.junit.experimental.results.resultmatchers org.junit.tests.parameterizedtesttest org.junit.tests.suitemethodtest org.junit.tests.suitetes t org.junit.tests.testlistenertest org.junit.tests.timeouttest org.junit.tests.allteststest org.junit.tests.ignoreclasstest org.junit.tests.prejunit4testcaserunnertest org.junit.tests.listenertest org.junit.tests.runnertest org.junit.tests.commandlinetest org.junit.experimental.theories.theor y org.junit.experimental.theories.parametersignature org.junit.experimental.theories.parameterssuppliedby org.junit.experimental.theories.suppliers.testedonsupplier org.junit.experimental.theories.suppliers.testedon org.junit.experimental.theories.datapoint org.junit.experimental.theories.potentialparametervalu e org.junit.experimental.theories.parametersupplier org.junit.experimental.theories.internal.theorymethod org.junit.tests.anotherpackage.super org.junit.experimental.theories.internal.parameterizedassertionerror org.junit.internal.runners.initializationerror org.junit.runners.parameterized org.junit.tests.anotherpackage.sub org.junit.runners.suite org.junit.tests.inaccessiblebaseclasstest org.junit.internal.runners.methodvalidator org.junit.beforeclass org.junit.afterclass org.junit.internal.runners.testclass org.junit.test org.junit.before org.junit.after org.junit.internal.runners.testmethod org.junit.internal.runners.junit4classrunner org.junit.internal.runners.classroadie org.junit.internal.runners.failedbefore org.junit.runner.manipulation.filterable org.junit.internal.runners.methodroadie org.junit.internal.runners.compositerunne r org.junit.runner.manipulation.notestsremainexception org.junit.runner.manipulation.filter org.junit.internal.requests.filterrequest org.junit.ignore org.junit.runner.runwith org.junit.tests.validationtest org.junit.internal.requests.classesrequest org.junit.runner.manipulation.sorter org.junit.runner.manipulation.sortable org.junit.internal.requests.ignoredclassrunner org.junit.runner.request org.junit.internal.requests.errorreportingrequest org.junit.runner.notification.runnotifier org.junit.runner.notification.stoppedbyuserexception org.junit.tests.annotateddescriptiontes t org.junit.internal.runners.errorreportingrunner org.junit.tests.userstoptest org.junit.internal.requests.sortingreques t org.junit.tests.classrequesttest org.junit.runner.runner org.junit.runners.alltests org.junit.runner.description org.junit.internal.runners.oldtestclassrunner org.junit.internal.runners.junit38classrunner junit.framework.junit4testadapter org.junit.tests.oldtestclassadaptinglistenertes t org.junit.samples.simpletest org.junit.tests.suitedescriptiontest junit.framework.junit4testadaptercache junit.framework.junit4testcasefacade org.junit.tests.testdescriptiontest org.junit.tests.junitcoretest org.junit.assume org.junit.runner.junitcore org.junit.assert org.junit.internal.arraycomparisonfailure org.junit.tests.runwithtest org.junit.samples.listtes t org.junit.tests.testclassmethodsrunnertes t org.junit.runner.result org.junit.comparisonfailure org.junit.runner.notification.failure org.junit.experimental.results.printableresul t org.hamcrest.stringdescription org.hamcrest.basedescription org.hamcrest.internal.arrayiterator org.junit.tests.failedconstructiontest org.junit.runner.notification.runlistener org.hamcrest.core.issame org.hamcrest.selfdescribing org.junit.experimental.results.failurelist org.hamcrest.core.isinstanceof org.hamcrest.internal.selfdescribingvalueiterator org.hamcrest.internal.selfdescribingvalue org.junit.tests.assumptiontest org.junit.tests.expectedtest org.hamcrest.core.isanything org.junit.internal.textlistener org.hamcrest.description org.hamcrest.core.is org.junit.tests.assertiontes t org.hamcrest.core.isequal org.junit.tests.textlistenertest org.hamcrest.corematchers org.hamcrest.core.isnu l org.hamcrest.core.isnot org.hamcrest.basematcher org.junit.tests.oldtestclassrunnertest org.hamcrest.core.anyof org.hamcrest.matcher org.junit.tests.assumptionviolatedexceptiontest org.junit.matchers.each org.junit.matchers.iscollectioncontaining org.hamcrest.core.describedas org.junit.tests.annotationtes t org.junit.matchers.combinablematcher org.junit.tests.eachtes t org.junit.matchers.junitmatchers org.hamcrest.core.allof org.junit.matchers.stringcontains org.junit.matchers.substringmatcher org.junit.tests.bothtes t org.junit.matchers.typesafematcher org.junit.tests.initializationerrorforwardcompatibilitytest org.junit.tests.forwardcompatibilitytes t junit.framework.test org.junit.tests.forwardcompatibilityprintingtest junit.framework.testfailure junit.runner.version junit.samples.simpletes t junit.tests.runner.textrunnertes t junit.framework.testsuit e junit.framework.testresult junit.framework.protectabl e junit.textui.resultprinter junit.framework.testlistene r junit.tests.runner.textfeedbacktes t junit.textui.testrunner junit.runner.basetestrunner junit.extensions.testsetup junit.extensions.testdecorator junit.extensions.repeatedtest junit.tests.runner.textrunnersinglemethodtest junit.tests.runner.stackfiltertest org.junit.tests.oldtests junit.tests.alltest s junit.tests.runner.alltest s junit.extensions.activetestsuite junit.tests.runner.basetestrunnertes t junit.tests.framework.success junit.samples.alltests junit.tests.extensions.repeatedtesttest junit.tests.framework.failure org.hamcrest.factory junit.tests.extensions.alltest s junit.tests.framework.notestcaseclass junit.samples.listtest junit.tests.extensions.extensiontest junit.framework.testcase junit.tests.wasrun org.junit.tests.sortabletest junit.tests.extensions.activetesttes t junit.runner.testrunlistene r junit.tests.framework.testimplementortest junit.tests.framework.notvoidtestcase junit.tests.framework.notestcases junit.tests.framework.notpublictestcas e junit.tests.framework.testcasetest junit.tests.framework.alltest s junit.tests.framework.suitetes t junit.tests.framework.overridetestcase junit.tests.framework.floatasserttes t junit.tests.framework.noargtestcasetest junit.tests.framework.onetestcase junit.tests.framework.testlistenertest junit.tests.framework.asserttest junit.tests.framework.comparisonfailuretes t junit.tests.framework.comparisoncompactortest junit.framework.assert junit.framework.assertionfailederror junit.tests.framework.inheritedtestcas e junit.framework.comparisonfailure junit.framework.comparisoncompactor junit.tests.framework.doubleprecisionasserttest org.junit.samples.money.moneytest junit.samples.money.moneybag junit.samples.money.money junit.samples.money.moneytes t junit.samples.money.imoney CQSE GmbH 2013 OO Design Metrics Fan-In Fan-Out 4 Coupling Cohesion Stability Depth of Inheritance # Children
Trugschluss: Messung == Bewertung Messung Bewertung
Trugschluss: Messung == Bewertung Messung Bewertung Kontext
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
Manuelle Reviews viele relevante Qualitätseigenschaften sind nicht automatisch analysierbar Bezeichner Kommentare sinnvolle Verwendung von Datenstrukturen und Algorithmen Konsistenz der Fehlerbehandlung logische Redundanz diese Eigenschaften bedürfen der manuelle Analyse manuelle Analysen werden in der Praxis nur spärlich eingesetzt manuelle Analysen sollten soweit möglich durch automatische Analysen unterstüzt werden
Reviews bei der ConQAT-Entwicklung flächendeckendes Review aller Änderungen auf Basis des Ampelzustands der angepassten Dateien Sammlung der Issues als TODO-Tags im Code review creation release review RED YELLOW GREEN deletion modification
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
Messung erfordert Ziele 4 Keine Defizite 3 Keine Defizite in geändertem Code 2 Keine neue Defizite 1 Egal
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
Quality Indicator Stability of Regular Team Builds Architecture Conformance Full Assessment Build is stable. There are zero violations. Delta Assessment At the time of the last report, there was no automated build at all. Architecture specification was completed and is now fully adhered to. Stability of Unit Tests 75 unit tests are passing but many tests have been disabled. Unit tests are now executed automatically. Code Coverage Cover coverage measurement is not yet enabled. is working on this. Compiler Warnings The code exhibits 7 compiler warnings. However, it is questionable if their removal is worthwhile. The number of warnings was significantly reduced but not all fixes actually improve the code quality. Coding Guidelines Violations Duplicated Code The threshold of for types with any FxCop warning is violated. With a clone coverage of the threshold ofi s slightly violated. The amount of files with at least one FxCop warning increased. Clone coverage did not change significantly. File Size The threshold regarding files > LOC is violated. Use of partial classes improves the metric values but does not improve code quality. Nesting Depth Both thresholds are satisfied. All nestings deeper than have been removed. Length of Longest Method The threshold regarding methods > LOC is violated. The amount of types with methods longer than decreased significantly. LoCM
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
»Die Sau wird vom Wiegen nicht fett«messung Bewertung Aktion Task-Liste Qualitäts- Verbesserung
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
ConQAT graphische DSL zur Spezifikation der Analysekonfiguration Prozessoren beschreiben Analyseoperationen Kanten beschreiben Datenfluss Blöcke ermöglichen die Wiederverwendung von Analysefunktionalität source modulea Utility.java Main.java Loader.java source modulea Utility.java 128 Main.java 244 Loader.java 331 source 703 modulea 703 Utility.java 128 Main.java 244 Loader.java 331 2011/04/14 703 2011/04/10 681 2011/03/05 620 2009/06/18 120 http://www.conqat.org
Qualitäts-Dashboards CQSE GmbH 2013
Eingabe Quelltext (Ada, ABAP, C/C++, Cobol, C#, Java, JavaScript, VB, PL/I, ) Textdokumente (Plain, CSV, Word, PDF) Modelle (Simulink/Stateflow) Respositories (Subversion, TFS, Bugzilla, Redmine) Analyse Basis-Metriken (# Files, LOC, SLOC, # Methods, Nesting Depth, ) Clone-Detection Architektur-Analyse Tool-Integration (PMD, Findbugs, JUnit, Cobertura, Klocwork, FxCop, ) Basis-Metriken (# Files, LOC, SLOC, # Methods, Nesting Depth, ) Historisierung Ausgabe verschiedene Formate (HTML, PDF, Text, ) Tabellen Diagramme (Balken, Linie, Radar, Fläche, Scatter, Torte, ) Tree-Maps (Plain & Cushion)
Teamscale SVN Incremental Analysis Engine Web Client Scheduler TFS NoSQL Store REST Service Layer Eclipse Worker 1 Worker N Visual Studio File System
DEMO CQSE GmbH 2013
DEMO CQSE GmbH 2013
DEMO CQSE GmbH 2013
DEMO CQSE GmbH 2013
DEMO CQSE GmbH 2013
500.000 LoC Clone LoC 50.000 450.000 45.000 400.000 40.000 350.000 35.000 300.000 30.000 250.000 25.000 200.000 20.000 150.000 15.000 100.000 10.000 50.000 5.000 0 0 Aug. 04 Okt. 04 Dez. 04 Feb. 05 Apr. 05 Jun. 05 Aug. 05 Okt. 05 Dez. 05 Feb. 06 Apr. 06 Jun. 06 Aug. 06 Okt. 06 Dez. 06 Feb. 07 Apr. 07 Jun. 07 Aug. 07 Okt. 07 Dez. 07 Feb. 08 Apr. 08 Jun. 08 Aug. 08 Okt. 08 Dez. 08 Feb. 09 Apr. 09
Quality Dashboard Quality Control Project Manager interprets Quality Reports creates interprets Quality Engineer defines configures reviews triggers/educates implements analyzes generates Teamscale Analysis Tool accesses Software System Data Sources respects interprets Guidelines Developer
Kontakt Dr. Florian Deißenböck deissenboeck@cqse.eu +49 179 7857188 CQSE GmbH Lichtenbergstraße 8 85748 Garching bei München @deissenboeck