Testing tools and AI - ideas what to try with some tool examples
Better Software: introduction to good code
1. Be#er
So(ware
An
introduc2on
to
good
code
Giordano
Scalzo,
06/05/2009
2. • Why code matters (10 min)
• Disclaimer
• Good and Bad Code
• The Broken Window Theory
• The Grand Redesign in the Sky
• The Boy Scout Rule
• OOP Patterns and Principles (30 min)
• SOLID Principles
• The Others Principles
• Good code: Smells and heuristics (30 min)
• Comments
• Functions
• General
• Names
• Test
• Conclusion (5 min)
• Q&A? (15 min)
24. public class PrintServerImpl extends ServiceAdvanced
implements PrintServer, JobListener{
public synchronized String createJob(Object data) { //...
}
public int getStatus(String jobId) { //...
}
public void print(String jobId, int startPage, int endPage) { //...
}
public byte[] getPreview(String jobId, int pageNum) { //...
}
public IRawData getData(String jobId) { //...
}
public void abortAction(String jobId) { //...
}
public Vector getPrinterList() { //...
}
public synchronized void setPrinterList(Vector printerList) { //...
}
public void statusChanged(JobEvent jobEvent) { //...
}
public void pageComputed(JobEvent jobEvent) { //...
}
// ...
}
25. public class PrinterServerJob {
public synchronized String createJob(Object data) { //...
}
public int getStatus() { //...
}
public void addDataToJob() { //...
}
public void print(){ //...
}
public void print(int startPage, int endPage){ //...
}
public byte[] getPreview(int pageNum){ //...
}
// ...
}
public class PrinterList {
public Vector getPrinterList(){ //...
}
public synchronized void setPrinterList(Vector printerList){ //...
}
}
public class JobEventListener{
public void statusChanged(JobEvent jobEvent){ //...
}
public void pageComputed(JobEvent jobEvent){ //...
}
}
26. public class PrintServerImpl extends ServiceAdvanced
implements PrintServer, JobListener{
private Map<String, PrinterServerJob> printerServerJob;
private PrinterList printerList;
private JobEventListener jobEventListener;
public PrintServerJob getJob(String jobId) {
return printerServerJob.get(jobId);
}
public Vector getPrinterList(){
return printerList.getPrinterList();
}
public void setPrinterList(Vector printerList){
return printerList.setPrinterList(printerList);
}
public void statusChanged(JobEvent jobEvent){
jobEventListener.statusChanged(jobEvent);
}
public void pageComputed(JobEvent jobEvent){
jobEventListener.pageComputed(jobEvent);
}
//...
}
28. public static final int TYPE_UNDEFINED = 0;
public static final int TYPE_LEGAL_AG = 1;
public static final int TYPE_LEGAL_PG = 2;
public static final int TYPE_TEMPORARY = 3;
//...
boolean ok = false;
String buildType = m_cdInfo.buildType.toUpperCase();
String prefix = "";
switch (archType) {
case TYPE_LEGAL_AG:
if (buildType.equals("LEGAL_AG")
|| buildType.equals("LEGAL_AGPG")){
ok = true;
}
break;
case TYPE_LEGAL_PG:
if (buildType.equals("LEGAL_PG")
|| buildType.equals("LEGAL_AGPG")){
ok = true;
}
break;
case TYPE_TEMPORARY:
if (buildType.equals("TEMPORARY")
|| buildType.equals("PRV")){
ok = true;
}
prefix = "AP ";
break;
}
if (!ok) {
BurnerHelper.showError(...);
}
29. public interface ArchiveType {
public boolean isOk(String buildType);
public String getPrefix();
}
public class Undefined implements ArchiveType {
public boolean isOk(String buildType){
return false;
}
public String getPrefix() {
return "";
}
}
public class LegalAg implements ArchiveType {
public boolean isOk(String buildType){
return buildType.equals("LEGAL_AG") ||
buildType.equals("LEGAL_AGPG")
}
public String getPrefix() {
return "";
}
}
30. public class LegalPg implements ArchiveType {
public boolean isOk(String buildType){
return buildType.equals("LEGAL_PG") ||
buildType.equals("LEGAL_AGPG")
}
public String getPrefix() {
return "";
}
}
public class Temporary implements ArchiveType {
public boolean isOk(String buildType){
return buildType.equals("TEMPORARY") ||
buildType.equals("PRV")
}
public String getPrefix() {
return "AP";
}
}
31. //...
archTypes.put(0, new Undefined());
archTypes.put(1, new LegalAg());
archTypes.put(2, new LegalPg());
archTypes.put(3, new Temporary());
//...
String buildType = m_cdInfo.buildType.toUpperCase();
boolean ok = archTypes.get(archType).isOk(buildType);
String prefix = archTypes.get(archType).getPrefix();
if (!ok) {
BurnerHelper.showError(...);
}
//...
32. Liskov Substitution Principle
If for each object o1 of type S there is an object o2 of type T such
that for all programs P defined in terms of T, the behavior of P is
unchanged when o1 is substituted for o2 then S is a subtype of T
34. public class Rectangle {
protected int _width;
protected int _height;
public int Width{
get { return _width; }
}
public int Height{
get { return _height; }
}
public virtual void SetWidth(int width){
_width = width;
}
public virtual void SetHeight(int height){
_height = height;
}
}
public class Square: Rectangle {
public override void SetWidth(int width){
_width = width;
_height = width;
}
public override void SetHeight(int height){
_height = height;
_width = height;
}
}
35. [TestFixture]
public class RectangleTests{
private void CheckAreaOfRectangle(Rectangle r){
r.SetWidth(5);
r.SetHeight(2);
Assert.IsEqual(r.Width * r.Height,10);
}
[Test]
public void PassingTest(){
Rectangle r = new Rectangle();
CheckAreaOfRectangle(r);
}
[Test]
public void FailingTest(){
Rectangle r = new Square();
CheckAreaOfRectangle(r);
}
}
36. public class Rectangle {
protected int _width;
protected int _height;
public int Width{
get { return _width; }
}
public int Height{
get { return _height; }
}
public virtual void SetWidth(int width){
_width = width;
}
public virtual void SetHeight(int height){
_height = height;
}
}
public class Square {
protected int _side;
public int Side{
get { return _side; }
}
public void SetSide(int side){
_side = side;
}
}
37. Interface Segregation Principle
Don’t be force to implement unused methods
Avoid “Fat Interfaces”
High cohesion - better understandability, robustness
Low coupling - better maintainability,
high resistance to changes
38. public interface CartographyListener {
public void poisChanged(Locations pois);
public void cellsChanged(Locations cells);
public void mapChanged(ImageIcon map);
public void updateZoomLevel(int level);
public void updateGeoArea(GeoArea ga);
public void updateGridPosition(Point2D.Double gridPosition);
public void updateMousePosition(Point position);
}
public class CellLayer extends Layer {
/* Methods from CartographyListener interface */
public void cellsChanged(Locations cells) {
setLocations(cells);
}
//.....
}
public class PoiLayer extends Layer {
/* Methods from CartographyListener interface */
public void poisChanged(Locations locations) {
setLocations(locations);
}
//.....
}
39. public abstract class Layer implements CartographyListener, DrawingInterface {
/* Methods from CartographyListener interface */
// Metto qui un'implementazione vuota (una sorta di adapter) così
// non sono costretta a sovrascrivere i metodi in tutte le specializzazioni.
public void poisChanged(Locations pois) {}
public void cellsChanged(Locations cells) {}
public void mapChanged(ImageIcon map) {}
public void updateZoomLevel(int level) {
m_zoomLevel = level;
}
public void updateGeoArea(GeoArea ga) {
m_geoArea = ga;
}
public void updateGridPosition(Point2D.Double gridPosition) {}
public void updateMousePosition(Point position) {}
/* End of methods from CartographyListener interface */
//.....
}
40. public class CartographyUI extends JPanel {
public void addCartographyListener(CartographyListener listener) {
if (m_listeners == null) {
m_listeners = new ArrayList();
}
m_listeners.add(listener);
}
public void setCelles(Locations celles) {
Iterator listeners = m_listeners.iterator();
while (listeners.hasNext()) {
CartographyListener listener =
(CartographyListener)listeners.next();
listener.cellsChanged(celles);
}
updateViewFromLocations();
}
public void setPois(Locations pois) {
Iterator listeners = m_listeners.iterator();
while (listeners.hasNext()) {
CartographyListener listener =
(CartographyListener)listeners.next();
listener.poisChanged(pois);
}
updateViewFromLocations();
}
//.....
}
41. public interface CartographyListener {
}
public interface CellListener extends CartographyListener {
public void cellsChanged(Locations cells);
}
public interface PoiListener extends CartographyListener {
public void poisChanged(Locations locations);
}
42. public class CartographyUI extends JPanel {
public void setCelles(Locations celles) {
Iterator listeners = m_listeners.iterator();
while (listeners.hasNext()) {
CellListener listener =
(CellListener)listeners.next();
listener.cellsChanged(celles);
}
updateViewFromLocations();
}
public void setPois(Locations pois) {
Iterator listeners = m_listeners.iterator();
while (listeners.hasNext()) {
PoiListener listener =
(PoiListener)listeners.next();
listener.poisChanged(pois);
}
updateViewFromLocations();
}
public void addCartographyListener(CartographyListener listener) {
Class<?> c = genericObj.getClass();
Class<?> interfaces[] = c.getInterfaces();
for (Class<?> implementedIntf : interfaces) {
if (implementedIntf.getName().equals(
urmetcns.mito.ui.cartography.ui_components.PoiListener"))
poiListeners.add((PoiListener)listener);
if (implementedIntf.getName().equals(
"urmetcns.mito.ui.cartography.ui_components.CellListener"))
cellListeners.add((CellListener)listener);
// ...
}
}
// ...
}
44. private boolean retriveCallSideInfo(String Side) {
//...
DeterminationMethod = getXmlOption("Method"); // specify which method to be used
//...
if (DeterminationMethod.equals("PhoneMatch")) {
//...
}
if (DeterminationMethod.equals("Exist")) {
//Query to che the existence of a specified dictionary element
sqlString= "SELECT count(*) AS Cnt FROM iri_dictionary "
" WHERE iri_id = ? and key_dictionary = ?";
pstmt = (OraclePreparedStatement)assocInfo.conn.prepareStatement(sqlString);
pstmt.setLong(1,assocInfo.myIRIId);
pstmt.setString(2,Dictionary);
}
if (DeterminationMethod.equals("Compare")) {
//...
}
if (DeterminationMethod.equals("InOnly")) {
//Query alwais true for the
//provider Telecom Internazionale
sqlString= "SELECT 1 As Cnt FROM Dual";
//...
}
//...
//return true if the info has been found
return (itemFound == 1);
45. public abstract class CallSideDeterminator {
public abstract boolean findItem();
//...
}
public class CallSideDeterminatorCompare extends CallSideDeterminator {
@Override
public boolean findItem(){
//...
}
}
public class CallSideDeterminatorDFDM extends CallSideDeterminator {
@Override
public boolean findItem(){
//...
}
}
public class CallSideDeterminatorPhoneMatch extends
CallSideDeterminator {
@Override
public boolean findItem(){
//...
}
}
46. public class AsExecFindCallSide extends AsCommandExec {
HashMap<String, CallSideDeterminator> strategies=
new HashMap<String, CallSideDeterminator>();
private void init() {
strategies= new HashMap<String, CallSideDeterminator>();
strategies.put("PhoneMatch", new CallSideDeterminatorPhoneMatch());
strategies.put("Compare", new CallSideDeterminatorCompare());
strategies.put("DFDM", new CallSideDeterminatorDFDM());
//...
}
protected boolean retrieveCallSideInfo(String side) {
CallSideDeterminator determinator = null;
if ((determinator = strategies.get(
getXmlOption("Method"))) != null) {
determinator.initDeterminator(assocInfo, side);
if(determinator.findItem()) {
return determinator.storeCIN(side);
}
}
return false;
}
//...
}
48. Reuse Release Equivalency Principle
The granule of reuse is the granule of release
A package can be considered unit of distribution
A release should have a version number
Black-box, package that is to be used but not changed
49. Common Closure Principle
Maintainability is more important than reusability
If code must change, all changes should be
in the same package
Common changing classes, should be in the same package
50. Common Reuse Principle
The classes in a package are reused together
If you reuse one of the classes in a package, you reuse them all
52. Least Astonishment Principle
The result of some operation should be obvious, consistent, predictable
Occam’s Razor: The simplest answer is usually the correct answer
53. int multiply(int a, int b) {
return a + b;
}
int write_to_file(const char* filename, const char* text){
printf("[%s]n", text); /* Note that 'filename' is unused */
}
54. Law of Demeter
Encapsulation
An object A can request a service (call a method) of
an object instance B, but object A cannot
“reach through” object B to access yet
another object, C, to request its services
An object should avoid invoking methods of
a member object returned by another method
“Don’t talk to stranger"
“Use only one dot"
60. //***********************************************************************************
//! Initalize procedure
/*!
* This methos is called from the main in order to initialize all the thinks<br>
* that the plugin need.
*
* param inLog log that the plugin can use for its own purpose
* return true = all ok false = intialization failed
*/
//***********************************************************************************
public bool init(log4net.ILog inLog)
{
this.log = inLog;
//log.Debug("=======================================================");
log.Debug("============ INIT Module " + getModuleName() + " =========");
return true;
}
63. /**
*
*
* 03 Oct 2005 - AB - Added the isSameCIDinSameLiuID() function to avoid
different CID in the same LIU (Ticket#2564)
* 09 Sep 2005 - AB - fixed the retriveCallSideInfo() for the PhoneMatch
method (Ticket#2381)
* 06 Sep 2005 - AB - Fixed the SearchProviderDate() to properly work
with the 'DATA' association technique
* 01 Sep 2005 - AB - Added the dupval index exception handling in
saveInHiddenJournal() function
* 27 Jul 2005 - AB - changed the isInformationInDb() to avoid exiting
with assocInfo.lemfList == null
* 27 Jul 2005 - AB - removed the updateJournal() function because not
needed
* 26 Jul 2005 - AB - Now mergeJournal() saves a copy of the two lius ti
be merged in the hidden_journal table
* 26 Jul 2005 - AB - Added the saveInHiddenJournal() function to enhance
the mergeJournal() function
* 05 Jul 2005 - AB - Changed the retriveCallSideInfo queries to select
the correct liu_id in every situation.
…
* 23 Mar 2005 - AB - Added the ORA-00001 error handling in the
AddIRI2Journal
* 9 Mar 2005 - AB - moved the queryExec body function to a generic
queryExec function in the IRITools
* 11 May 2004 - AB - Started
**/
64. svn ci -m ‘Added the isSameCIDinSameLiuID() (Ticket#2564)’ AssociationFunction.java
78. protected void build(DynamicView view, File baseTemplate, File outTemplate)
throws ArchiverException, WrEntsException {
//Create required data and column description
List colNames = normalize(view.getDynamicColumnSet());
HashMap lines = new HashMap();
List leftNames = new ArrayList();
List rightNames = new ArrayList();
//Start reading input template and create output copy
BufferedReader r = null;
FileWriter w = null;
if (outTemplate.exists()) {
outTemplate.delete();
}
try {
r = new BufferedReader(new FileReader(baseTemplate));
w = new FileWriter(outTemplate);
String record = null;
boolean sortedBlock = false;
while ((record = r.readLine()) != null) {
if (sortedBlock) {
if (record.toUpperCase().indexOf(
"{END_SORTED_RECS}") >= 0) {
sortedBlock = false;
//Writes required records
String line = null;
//Static first line (if any)...
for (int j = 0; j < leftNames.size(); j++) {
line = (String)lines.get(leftNames.get(j));
if (line != null) {
w.write(line + "n");
}
}
79. //Sorted lines
for (int j = 0; j < colNames.size(); j++) {
line = (String)lines.get(colNames.get(j));
if (line != null) {
w.write(line + "n");
}
}
w.write(record + "n");
//Static last line (if any)...
for (int j = 0; j < rightNames.size(); j++) {
line = (String)lines.get(rightNames.get(j));
if (line != null) {
w.write(line + "n");
}
}
}
else {
int index = record.indexOf("{META_REC:");
if (index >= 0) {
String key = record.substring(index + 10,
record.indexOf('}', index));
if (key.indexOf(":") >= 0) {
String values[] = key.split(":");
if (values[1].equals("L")) {
leftNames.add(key);
}
else if (values[1].equals("R")) {
rightNames.add(key);
}
}
lines.put(key, new String(record));
}
}
}
92. Dead Code
Code that isn’t executed
if statement that checks
for a condition that can’t happen
Private method never called
switch/case conditions that
never occur
Do the right thing: give it a decent burial
93. Vertical Separation
Variables and function should be defined close
to where they are used
Local variables
should be declared just above their first usage
94. public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/////si aggancia al file salvato nella mappa dell'oggetto application
String fileId = request.getParameter("FILEID");
if (fileId == null) {
throw new ServletException("Invalid FileId");
}
String partNum = request.getParameter("PART");
int part = 1;
if (partNum != null) {
part = Integer.valueOf(partNum).intValue();
}
boolean isLast = "Y".equals(request.getParameter("LAST"));
boolean getName = "Y".equals(request.getParameter("GETNAME"));
String fileName = MitoDownloadCache.INSTANCE.getFileName(fileId, part);
if (fileName== null) {
throw new ServletException("Invalid FileName");
}
MitoConfig mitoCfg = new MitoConfig();
File file = new File(mitoCfg.getPrintClientDataPath()+"/"+fileName);
if (!file.exists()) {
throw new ServletException("File " + file.getAbsolutePath()
+ " not found");
}
if (getName) {
doDownloadFilename(request, response, file.getName());
} else {
if (isLast) {
MitoDownloadCache.INSTANCE.removeFileList(fileId);
}
doDownload(request, response, file);
file.delete();
}
95. public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/////si aggancia al file salvato nella mappa dell'oggetto application
String fileId = request.getParameter("FILEID");
if (fileId == null) {
throw new ServletException("Invalid FileId");
}
String partNum = request.getParameter("PART");
int part = 1;
if (partNum != null) {
part = Integer.valueOf(partNum).intValue();
}
String fileName = MitoDownloadCache.INSTANCE.getFileName(fileId, part);
if (fileName== null) {
throw new ServletException("Invalid FileName");
}
MitoConfig mitoCfg = new MitoConfig();
File file = new File(mitoCfg.getPrintClientDataPath()+"/"+fileName);
if (!file.exists()) {
throw new ServletException("File " + file.getAbsolutePath()
+ " not found");
}
boolean getName = "Y".equals(request.getParameter("GETNAME"));
if (getName) {
doDownloadFilename(request, response, file.getName());
} else {
boolean isLast = "Y".equals(request.getParameter("LAST"));
if (isLast) {
MitoDownloadCache.INSTANCE.removeFileList(fileId);
}
doDownload(request, response, file);
file.delete();
}
}
116. Avoid Encodings
Names should not be encoded with type or
scope information
Hungarian Notation as obsolete legacy
117. protected int m_FilesCount; // Numero files contenuti nel job
protected int m_VolumesCount; // Numero dischi richiesti dal job (1 sola copia)
protected long m_TotalSize; // Size totale in byte del job
protected int m_VolumesDone; // Numero dischi completati
protected String m_VolDoneList; // Lista dischi completati (durante esecuzione)
protected ArrayList m_labelFields; // Nomi/valori campi da utilizzare in label
private long m_LastModTime; // Data/ora modifica file informativo del job
protected boolean isOnDisk;
118. protected int filesCount; // Numero files contenuti nel job
protected int volumesCount; // Numero dischi richiesti dal job (1 sola copia)
protected long totalSize; // Size totale in byte del job
protected int volumesDone; // Numero dischi completati
protected String volDoneList; // Lista dischi completati (durante esecuzione)
protected ArrayList labelFields; // Nomi/valori campi da utilizzare in label
private long lastModTime; // Data/ora modifica file informativo del job
protected boolean isOnDisk;
123. Insufficient Tests
How many tests? A test suite should test everything that could possibly break
Use a Coverage Tool:
Java
EMMA
Cobertura
Clover
.NET
PartCover
C/C++
BullseyeCoverage
gcov
Beware of Coverage Results
124. Don’t Skip Trivial Tests
They are better than documentation
Easy today, maybe hard tomorrow
126. Tests Should Be Fast
If slow, launched less frequentely
If launched less frequentely, big change between launches and difficult to
know where and when an error was introduced