Refactoring In Tdd The Missing Part

Loading...

Flash Player 9 (or above) is needed to view presentations.
We have detected that you do not have it on your computer. To install it, go here.

0 comments

Post a comment

    Post a comment
    Embed Video
    Edit your comment Cancel

    4 Favorites

    Refactoring In Tdd The Missing Part - Presentation Transcript

    1. Test Driven Development
    2. Debugging Sucks Testing Rocks
    3. Bugfixing Cost Bugs • Cost of bugfix = Number of bugs x Cost per fix, but... • How many bugs do you plan to have? • How hard do you think they are to fix?
    4. Bugfixing Tests Cost Bugs/Features • Cost of tests = Number and complexity of features • How many features do you plan to have? • How complex they are?
    5. Test Automation Lifecycle
    6. Fixture all the things we need to have in place in order to run a test and expect a particular outcome
    7. Fixture Setup Fixture Steps Fixture Setup
    8. SUT System Under Test is whatever thing we are testing
    9. Exercise SUT Fixture Steps SUT Fixture Setup Exercise SUT
    10. Verify Result Fixture Steps SUT Fixture Setup Exercise SUT Verify Result
    11. “Don't call us, we'll call you”
    12. Fixture Teardown Fixture Steps SUT Fixture Setup Exercise SUT Verify Result Fixture Teardown
    13. a test is a good one if... • Is really automatic • Should be easy to invoke one or more tests • Must determine for itself whether it passed or failed • Test everything that’s likely to break • Must be independent from the environment and each other test • Should be repeatable, could be run over and over again, in any order and produce the same results • The code is clean as the production code
    14. the kind of test is not determined by the used tool
    15. Unit tests A test is not a unit test if: 1. It talks to a database 2. It communicates across the network 3. It touches the file system 4. You have to do things to your environment to run it (eg, change config files) Tests that do this are integration tests Michael Feathers
    16. public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); } Simple Unit Test
    17. public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); } Fixture Setup
    18. public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); } Exercise SUT
    19. public void marriageIsSimmetric() { Customer alice = new Customer("alice"); Customer bob = new Customer("bob"); bob.marry(alice); assertTrue(bob.isMarriedTo(alice)); assertTrue(alice.isMarriedTo(bob)); } Verify Result
    20. Test-Driven Development
    21. As a developer, I want to learn TDD, so that I can write clean code that works
    22. Clean code that works Clean code is simple and direct. Clean code reads like well-written prose. Clean code never obscures the designer’s intent but rather is full of crisp abstractions and straightforward lines of control Grady Booch
    23. Clean code that works Clean code always looks like it was written by someone who cares. There is nothing obvious that you can do to make it better. All of those things were thought about by the code’s author, and if you try to imagine improvements, you’re led back to where you are, sitting in appreciation of the code someone left for you, code left by someone who cares deeply about the craft. Michael Feathers
    24. Clean code that works You know you are working on clean code when each routine you read turns out to be pretty much what you expected.You can call it beautiful code when the code also makes it look like the language was made for the problem Ward Cunningham
    25. Simple design The code is simple enough when it: 0. Pass all the tests 1. Expresses every idea that we need to express 2. Contains no duplication 3. Has the minimum number of classes and functions (In this order) Adapted from Extreme Programming Installed by Ron Jeffries et al.
    26. Clean code that works • First we'll solve the “that works” part • Then we'll solve the “clean code” part
    27. Code that works could Smell Refactoring by Martin Fowler
    28. ... then Refactor Is the process of changing a software system in such a way that it does not alter the external behaviour of the code yet improves its internal structure Martin Fowler
    29. through small steps Refactoring by Martin Fowler
    30. ... fight the Smells Smell Common Refactorings Duplicated Code Extract Method, Extract Class, Pull-Up Method, Template Method Feature Envy Move Method, Move Field, Extract Method Extract Class, Extract Subclass, Extract Interface, Replace Data Large Class Value with Object Extract Method, Replace Temporary Variable with Query, Replace Long Method Method with Method Object, Decompose Conditional
    31. ... fight the Smells Smell Common Refactorings Shotgun Surgery Move Method, Move Field, Inline Class Replace Parameter with Method, Introduct Parameter Object, Long Parameter List Preserve Whole Object Data Class Move Method, Encapsulate Field, Encapsulate Collection Comments Extract Method, Introduce Assertion
    32. an example of Refactoring and Unit tests
    33. public class VobasBackupService implements Runnable { public void run() { Map fileDetails = new Hashtable(); try { BufferedReader reader = new BufferedReader(new FileReader(MainFrame.WATCHED_DATA)); String directoryName = reader.readLine(); File fileData = new File(directoryName, ".vobas"); while (directoryName != null) { if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream(new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } } File[] files = new File(directoryName).listFiles(); for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (fileDetail == null) { ScpTo.send(directoryName + File.separatorChar + file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { ScpTo.send(directoryName + File.separatorChar + file.getName()); fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } } ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream(new File(directoryName, ".vobas"))); for (FileDetail fileDetail : fileDetails.values()) { objectOutput.writeObject(fileDetail); } objectOutput.close(); directoryName = reader.readLine(); } reader.close(); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) {
    34. WTF?!?
    35. import java.util.*; public class FileDetail { make it private Date lastModified; private String fileName; public FileDetail(Date lastModified, String fileName) { this.lastModified = lastModified; compile this.fileName = fileName; } public Date getModificationDate() { return this.lastModified; } public String getName() { return this.fileName; } } public class MainFrame { } public static final String WATCHED_DATA = "/dev/null"; stub public class ScpTo { public static void send(String filePathToSend) { } // TODO: no need to implement :-( dependencies }
    36. is supposed to work, but how it works?
    37. prepare your engine :-) import org.junit.* ; import org.junit.* ; import static org.junit.Assert.* ; import static org.junit.Assert.* ; public class StubTest { public class StubTest { @Test @Test public void shouldAlwaysWork() { public void shouldNeverWork() { assertTrue(true); assertTrue(false); } } } }
    38. Untestable public void run() { try { Map fileDetails = new Hashtable(); BufferedReader reader = new BufferedReader(new FileReader(MainFrame.WATCHED_DATA)); ... } catch (FileNotFoundException e) { ... } }
    39. Extract Method public void run() { try { dontKnowWhatItDoes(MainFrame.WATCHED_DATA); } catch (FileNotFoundException e) { ... } public void dontKnowWhatItDoes(String directoryPathToBackup) throws ... { BufferedReader reader = new BufferedReader(new FileReader(directoryPathToBackup)); Map fileDetails = new Hashtable(); String directoryName = reader.readLine(); File fileData = new File(directoryName, ".vobas"); while (directoryName != null) { ... } reader.close(); }
    40. doing refactoring without tests is unsafe
    41. Characterization Test public class VobasBackupServiceCharacterizationTest { private List<File> directoriesToCleanup; @Before public void setUp() throws Exception { directoriesToCleanup = new ArrayList<File>(); } @After public void tearDown() throws Exception { for (File directoryToCleanup : directoriesToCleanup) { deleteDirectory(directoryToCleanup); } ScpTo.sended = new ArrayList<String>(); } @Test public void backupOneDirectoryWithOneFreshFile() throws Exception { VobasBackupService service = new VobasBackupService(); File oneDirectoryWithOneFile = createDirectoryToBackupWithFiles(1); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithOneFile); service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(1, ScpTo.sended.size()); directoriesToCleanup.add(oneDirectoryWithOneFile); }
    42. Characterization Test @Test public void backupOneDirectoryWithTwoFreshFiles() throws Exception { VobasBackupService service = new VobasBackupService(); File oneDirectoryWithTwoFiles = createDirectoryToBackupWithFiles(2); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithTwoFiles); service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(2, ScpTo.sended.size()); directoriesToCleanup.add(oneDirectoryWithTwoFiles); } @Test public void backupTwoDirectoriesWithOneFreshFile() throws Exception { VobasBackupService service = new VobasBackupService(); File oneDirectoryWithOneFile = createDirectoryToBackupWithFiles(1); File anotherDirectoryWithOneFile = createDirectoryToBackupWithFiles(1); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile( oneDirectoryWithOneFile, anotherDirectoryWithOneFile); service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(2, ScpTo.sended.size()); directoriesToCleanup.add(oneDirectoryWithOneFile); directoriesToCleanup.add(anotherDirectoryWithOneFile); }
    43. Magic Number @Test public void backupOneDirectoryWithTwoFreshFiles() throws Exception { VobasBackupService service = new VobasBackupService(); File oneDirectoryWithTwoFiles = createDirectoryToBackupWithFiles(2); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithTwoFiles); service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(2, ScpTo.sended.size()); directoriesToCleanup.add(oneDirectoryWithTwoFiles); } replace Magic Number with Expression @Test public void backupOneDirectoryWithTwoFreshFiles() throws Exception { VobasBackupService service = new VobasBackupService(); File oneDirectoryWithTwoFiles = createDirectoryToBackupWithFiles(2); File listOfDirectoriesToBackup = listOfDirectoriesToBackupIntoFile(oneDirectoryWithTwoFiles); service.backup(listOfDirectoriesToBackup.getAbsolutePath()); assertEquals(oneDirectoryWithTwoFiles.list().length, ScpTo.sended.size()); directoriesToCleanup.add(oneDirectoryWithTwoFiles); }
    44. Syntax Noise private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { String[] children = directoryToDelete.list(); for (int i=0; i<children.length; i++) { deleteDirectory(new File(directoryToDelete, children[i])); } } if (!directoryToDelete.delete()) { throw new Exception("unable to delete " + directoryToDelete.getAbsolutePath()); } } replace For with Loop private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { for (File child : directoryToDelete.listFiles()) { deleteDirectory(child); } } assert directoryToDelete.delete() : "unable to delete " + directoryToDelete.getAbsolutePath()); }
    45. Syntax Noise private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { String[] children = directoryToDelete.list(); for (int i=0; i<children.length; i++) { deleteDirectory(new File(directoryToDelete, children[i])); } } if (!directoryToDelete.delete()) { throw new Exception("unable to delete " + directoryToDelete.getAbsolutePath()); } } replace Test with Assertion private void deleteDirectory(File directoryToDelete) throws Exception { if (directoryToDelete.isDirectory()) { for (File child : directoryToDelete.listFiles()) { deleteDirectory(child); } } assert directoryToDelete.delete() : "unable to delete " + directoryToDelete.getAbsolutePath()); }
    46. now we can play safe // read all directories to backup from a file BufferedReader reader = new BufferedReader(new FileReader(directoryPathToBackup)); // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) { // get details on files in these directory Map fileDetails = new Hashtable(); File fileData = new File(directoryName, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } }
    47. now we can play safe // select only files to backup in directory File[] files = new File(directoryName).listFiles(new FilenameFilter() { public boolean accept(File directory, String fileName) { return ! fileName.equals(".vobas"); } }); // for each of those files for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); // if no previous details are given if (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); // if details are given but file has been modified } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } }
    48. now we can play safe // save all details on files to .vobas file ObjectOutput objectOutput = new ObjectOutputStream( new FileOutputStream(new File(directoryName, ".vobas"))); for (Object value : fileDetails.values()) { FileDetail fileDetail = (FileDetail) value; objectOutput.writeObject(fileDetail); } objectOutput.close(); // next directory to backup please... directoryName = reader.readLine(); } reader.close();
    49. narrow your target and your tests // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) { Comment // read details on files in directory Map fileDetails = new Hashtable(); File fileData = new File(directoryName, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } }
    50. Extract Method // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) { // read details on files in directory Map fileDetails = new Hashtable(); File fileData = new File(directoryName, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } }
    51. Extract Method public Map readDetailsOnFilesFrom(String directoryPath) throws ... { Map fileDetails = new Hashtable(); File fileData = new File(directoryPath, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream( new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } } return fileDetails; }
    52. Extract Method // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) { // read details on files in directory Map fileDetails = readDetailsOnFilesFrom(directoryName); // select only files to backup in directory File[] files = new File(directoryName).listFiles(new FilenameFilter() { public boolean accept(File directory, String fileName) { return ! fileName.equals(".vobas"); } }); ...
    53. Extract Method // for each of those directory String directoryName = reader.readLine(); while (directoryName != null) { Map fileDetails = readDetailsOnFilesFrom(directoryName); // select only files to backup in directory File[] files = new File(directoryName).listFiles(new FilenameFilter() { public boolean accept(File directory, String fileName) { return ! fileName.equals(".vobas"); } }); ...
    54. Duplicated Code, Complex Conditional // if no previous details are given if (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); // if details are given but file has been modified } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    55. Consolidate duplicated conditional fragments // if no previous details are given if (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); // if details are given but file has been modified } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.remove(file.getName()); fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    56. Consolidate duplicated conditional fragments // if no previous details are given if (fileDetail == null) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); // if details are given but file has been modified } else if (file.lastModified() > fileDetail.getModificationDate().getTime()) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    57. Consolidate duplicated conditional fragments // if no previous details are given or // if details are given but file has been modified if ((fileDetail == null) || (file.lastModified() > fileDetail.getModificationDate().getTime())) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    58. Extract Method for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file)) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } } private boolean noPrevioiusDetailsAreGiven(FileDetail fileDetail) { return fileDetail == null; } private boolean fileHasBeenModifiedSinceLastBackup(FileDetail fileDetail, File file) { return file.lastModified() > fileDetail.getModificationDate().getTime(); }
    59. Extract Method for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } } private boolean needBackup(FileDetail fileDetail, File file) { return noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file); }
    60. Rename Method public void dontKnowWhatItDoes(String directoryPathToBackup) throws ... { ... } public void backupDirectories(String listOfDirectoriesToBackup) throws ... { for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { // send to backup ScpTo.send(directoryName + File.separatorChar + file.getName()); // save details fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); } }
    61. Extract Method public void backupDirectories(String listOfDirectoriesToBackup) throws ... { ... for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } ... } private void backupFile(File fileToBackup) { ScpTo.send(file.getAbsolutePath()); } private void updateFileDetails(Map fileDetails, File file) { fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    62. Extract Method public void backupDirectories(String listOfDirectoriesToBackup) throws ... { BufferedReader reader = new BufferedReader(new FileReader(listOfDirectoriesToBackup)); String directoryPath = reader.readLine(); while (directoryPath != null) { backupDirectory(directoryPath); directoryPath = reader.readLine(); } reader.close(); } public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath); File[] files = filesToBackupInto(directoryPath); for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }
    63. Replace temporary variable with Query public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath); File[] files = filesToBackupInto(directoryPath); for (File file : files) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }
    64. Replace temporary variable with Query public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }
    65. Introduce Generics public void backupDirectory(String directoryPath) throws ... { Map fileDetails = readDetailsOnFilesFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { FileDetail fileDetail = (FileDetail) fileDetails.get(file.getName()); if (needBackup(fileDetail, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }
    66. Introduce Generics public void backupDirectory(String directoryPath) throws ... { Map<String, FileDetail> fileDetails = readDetailsOnFilesFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { if (needBackup(fileDetails, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }
    67. Inconsistent Name public void backupDirectories(String listOfDirectoriesToBackup) throws ... { BufferedReader reader = new BufferedReader(new FileReader(listOfDirectoriesToBackup)); String directoryPath = reader.readLine(); while (directoryPath != null) { backupDirectory(directoryPath); directoryPath = reader.readLine(); } reader.close(); }
    68. Extract Method, replace For with Loop public void run() { try { backupDirectories(listOfDirectoriesToBackup(MainFrame.WATCHED_DATA)); ... } } public List<File> listOfDirectoriesToBackup(String listOfDirectories) throws ... { List<File> directoriesToBackup = new ArrayList<File>(); BufferedReader reader = new BufferedReader(new FileReader(listOfDirectories)); String directoryName = reader.readLine(); while (directoryName != null) { directoriesToBackup.add(new File(directoryName)); directoryName = reader.readLine(); } return directoriesToBackup; } public void backupDirectories(List<File> listOfDirectoriesToBackup) throws ... { for (File directoryToBackup : listOfDirectoriesToBackup) { backupDirectory(directoryToBackup.getAbsolutePath()); } }
    69. public void run() { try { backupDirectories(listOfDirectoriesToBackup(MainFrame.WATCHED_DATA)); } catch (Exception e) { throw new RuntimeException(e); } } public void backupDirectories(List<File> listOfDirectoriesToBackup) throws ... { for (File directoryToBackup : listOfDirectoriesToBackup) { backupDirectory(directoryToBackup.getAbsolutePath()); } } public void backupDirectory(String directoryPath) throws ... { Map<String, FileDetail> fileDetails = readDetailsOnFilesFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { if (needBackup(fileDetails, file)) { backupFile(file); updateFileDetails(fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, fileDetails); }
    70. Feature Envy, Large Class public void writeDetailsOnFilesTo(String directoryPath, Map<String, FileDetail> fileDetails) throws ... { ... } public Map<String, FileDetail> readDetailsOnFilesFrom(String directoryPath) throws ... { ... } private boolean needBackup(Map<String, FileDetail> fileDetails, File file) { FileDetail fileDetail = fileDetails.get(file.getName()); return noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file); } private boolean noPrevioiusDetailsAreGiven(FileDetail fileDetail) { return fileDetail == null; } private boolean fileHasBeenModifiedSinceLastBackup(FileDetail fileDetail, File file) { return file.lastModified() > fileDetail.getModificationDate().getTime(); } private void updateFileDetails(Map<String, FileDetail> fileDetails, File file) { fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    71. Primitive Obsession, Data Clumps public void writeDetailsOnFilesTo(String directoryPath, Map<String, FileDetail> fileDetails) throws ... { ... } public Map<String, FileDetail> readDetailsOnFilesFrom(String directoryPath) throws ... { ... } private boolean needBackup(Map<String, FileDetail> fileDetails, File file) { FileDetail fileDetail = fileDetails.get(file.getName()); return noPrevioiusDetailsAreGiven(fileDetail) || fileHasBeenModifiedSinceLastBackup(fileDetail, file); } private boolean noPrevioiusDetailsAreGiven(FileDetail fileDetail) { return fileDetail == null; } private boolean fileHasBeenModifiedSinceLastBackup(FileDetail fileDetail, File file) { return file.lastModified() > fileDetail.getModificationDate().getTime(); } private void updateFileDetails(Map<String, FileDetail> fileDetails, File file) { fileDetails.put(file.getName(), new FileDetail(new Date(), file.getName())); }
    72. Extract Class class BackupReport { public Map<String, FileDetail> fileDetails; private BackupReport(File directoryToBackup) throws ... { fileDetails = new Hashtable<String, FileDetail>(); File fileData = new File(directoryToBackup, ".vobas"); if (!fileData.exists()) { fileData.createNewFile(); } else { ObjectInputStream fileDetailsReader = new ObjectInputStream(new FileInputStream(fileData)); FileDetail fileDetail = (FileDetail) fileDetailsReader.readObject(); while (fileDetail != null) { fileDetails.put(fileDetail.getName(), fileDetail); try { fileDetail = (FileDetail) fileDetailsReader.readObject(); } catch (EOFException e) { break; } } } } public static BackupReport readFrom(File directoryToBackup) throws ... { return new BackupReport(directoryToBackup); } public static BackupReport readFrom(String pathToDirectoryToBackup) throws ... { return new BackupReport(new File(pathToDirectoryToBackup)); } }
    73. Extract Class public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { if (needBackup(lastBackupReport.fileDetails, file)) { backupFile(file); updateFileDetails(lastBackupReport.fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, lastBackupReport.fileDetails); }
    74. Feature Envy public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { if (needBackup(lastBackupReport.fileDetails, file)) { backupFile(file); updateFileDetails(lastBackupReport.fileDetails, file); } } writeDetailsOnFilesTo(directoryPath, lastBackupReport.fileDetails); }
    75. Move Method, Rename Method public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { if (lastBackupReport.needBackup(file)) { backupFile(file); lastBackupReport.update(file); } } lastBackupReport.save(); }
    76. Inconsistent Name public void backupDirectory(String directoryPath) throws ... { BackupReport lastBackupReport = BackupReport.readFrom(directoryPath); for (File file : filesToBackupInto(directoryPath)) { if (lastBackupReport.needBackup(file)) { backupFile(file); lastBackupReport.update(file); } } lastBackupReport.save(); }
    77. Rename Class, Replace Data Value with Object public void backupDirectory(File directoryToBackup) throws ... { DirectoryBackupStatus directoryBackupStatus = new DirectoryBackupStatus(directoryToBackup); for (File file : filesToBackupInto(directoryToBackup)) { if (directoryBackupStatus.needBackup(file)) { backupFile(file); directoryBackupStatus.update(file); } } directoryBackupStatus.save(); }
    78. public void run() { try { backupDirectories(listOfDirectoriesToBackup(MainFrame.WATCHED_DATA)); } catch (Exception e) { throw new RuntimeException(e); } } public void backupDirectories(List<File> listOfDirectoriesToBackup) throws ... { for (File directoryToBackup : listOfDirectoriesToBackup) { backupDirectory(directoryToBackup); } } public void backupDirectory(File directoryToBackup) throws ... { DirectoryBackupStatus directoryBackupStatus = new DirectoryBackupStatus(directoryToBackup); for (File file : filesToBackupInto(directoryToBackup)) { if (directoryBackupStatus.needBackup(file)) { backupFile(file); directoryBackupStatus.update(file); } } directoryBackupStatus.save(); }
    79. class BackupPlan { public BackupPlan(File configuration) throws BackupFailedException { this.directoriesToBackup = directoriesToBackup(backupPlan); } public void runWith(BackupService backupService) throws BackupFailedException { for (File directoryToBackup : directoriesToBackup) { new DirectoryBackupService(directoryToBackup).backupWith(backupService); } } private List<File> directoriesToBackup(File configuration) throws BackupFailedException { ... } private List<File> directoriesToBackup; }
    80. class DirectoryBackupService { public DirectoryBackupService(File directoryToBackup) throws BackupFailedException { readBackupStatusFrom(directoryToBackup); } public void backupWith(BackupService backupService) throws BackupFailedException { for (File file : listOfFilesToBackup()) { if (needBackup(file)) { backupService.backup(file); update(file); } } save(); } ... }
    81. Test-Driven Development Cycle
    82. Write a test public class AdderTest { @Test public void testTwoPlusThree() { Adder a = new Adder(); assertEquals(5, a.add(2, 3)); } }
    83. Now it compiles public class AdderTest { @Test public void testTwoPlusThree() { Adder a = new Adder(); assertEquals(5, a.add(2, 3)); } } public class Adder { public int add(int a, int b) { return 0; } }
    84. Red bar! public class AdderTest { @Test public void testTwoPlusThree() { Adder a = new Adder(); assertEquals(5, a.add(2, 3)); } } public class Adder { public int add(int a, int b) { return 0; } } Expected 5, was 0
    85. Just pretend public class AdderTest { @Test public void testTwoPlusThree() { Adder a = new Adder(); assertEquals(5, a.add(2, 3)); } } public class Adder { public int add(int a, int b) { return 5; } }
    86. Remove the duplicated “5” public class AdderTest { @Test public void testTwoPlusThree() { Adder a = new Adder(); assertEquals(5, a.add(2, 3)); } } public class Adder { public int add(int a, int b) { return a+b; } }
    87. The procedure 1. Write a test 2. Make it compile Expected 5, was 0 3. Make it pass 4. Refactor
    88. Red Refactor Green Repeat every 2-10 min.
    89. It’s not about testing • TDD is a design technique • The tests are not the main point • The design emerges in small steps
    90. Clean code, why?
    91. Clean code, why? • Design is the great accelerator: • If you drop quality for speed, you will get neither • If you aim for quality... • ... and you know how to get it... • ... you will also be fast!
    92. Test first, why? • You think code from the point of view of the caller • This perspective makes for better design • Test coverage is a useful byproduct
    93. Refactor, why?
    94. Refactor, why? • What is clean code today could be bad code tomorrow • Refactoring is when I do design • Design emerges, with thought, care and small steps • I don’t claim I can guess the right design • Because I can: the tests support refactoring
    95. No silver bullet • Needs lots of practice • Requires discipline • Must think and be alert at all times!

    + Gabriele LanaGabriele Lana, 2 months ago

    custom

    783 views, 4 favs, 1 embeds more stats

    More info about this document

    © All Rights Reserved

    Go to text version

    • Total Views 783
      • 782 on SlideShare
      • 1 from embeds
    • Comments 0
    • Favorites 4
    • Downloads 21
    Most viewed embeds
    • 1 views on http://www.slideshare.net

    more

    All embeds
    • 1 views on http://www.slideshare.net

    less

    Flagged as inappropriate Flag as inappropriate
    Flag as inappropriate

    Select your reason for flagging this presentation as inappropriate. If needed, use the feedback form to let us know more details.

    Cancel
    File a copyright complaint
    Having problems? Go to our helpdesk?

    Categories