Case study: factoring integer numbers ===================================== The objectives of this class: - To illustrate a design methodology - Coding for the tests - Refactoring - Commenting - To show some tools: - Javadoc - CheckStyle - Junit % Write a class PrimeFactorizer, that, given an integer number, returns an array with all the prime factors of this number. % 1) Let's start by writing a unit test. When should we write unit tests, after or before we have coded the application? package juTestingEx; import junit.framework.TestCase; public class TestFactors extends TestCase { public TestFactors(String name) { super(name); } public void testEx() throws Exception { int tmp = 2 + 2; assertEquals(4, tmp); } } 1.1) What are good tests? - automated and repeatable. - Are my colleagues going to get the same results as me? - easy to implement. - Does it take me a few minutes to write? - once written, remain for future use. - Can I run my tests from two years ago? - run at the push of the button. - Can I run all the tests just pressing enter? - orthogonal - Does each test implements non-redundant behavior? 2) What is the simplest test case that our factorizer should pass? public void testTwo() throws Exception { int factors[] = PrimeFactorizer.factor(2); assertEquals(1, factors.length); assertEquals(2, factors[0]); } * We wrote the test without really having the program to be tested. This is called intentional programming. 3) What is the smallest program that we can create so that our app compiles? package juTestingEx; // Test error public class PrimeFactorizer { public static int[] factor(int i) { return null; } } 4) What is the difference between a test error and a test failure? 5) What is the minimum program that we can write so that our application passes the test? package juTestingEx; public class PrimeFactorizer { public static int[] factor(int i) { return new int[] {2}; } } 6) Let's enable checkstyle. How to fix the problems? package com.primeFactorizer; /** * This class factorizes a prime number. * @author fpereira * */ public final class PrimeFactorizer { /** * Private constructor to avoid instantiation. */ private PrimeFactorizer() { } /** * This method factors an integer number. * @param i the number to be factored out. * @return an array of integers, where each element is a factor of i. */ public static int[] factor(final int i) { return new int[] {2}; } } 6.1) Why to declare the field 'i' as final? - Thread safety - Minor compiler optimizations - Documentation 6.2) Can an object change, if it is declared final? 7) What is the simplest test case we can add to this? public final void testThree() throws Exception { int[] factors = PrimeFactorizer.factor(3); assertEquals(1, factors.length); assertEquals(3, factors[0]); } 8) And what is the simplest way to make our program to pass the test? public static int[] factor(final int i) { if (i == 2) { return new int[] {2}; } else { return new int[] {3}; } } 9) What is refactoring? 9.1) How could we refactor this code, to make it more general? public static int[] factor(final int i) { return new int[] {i}; } 10) The name 'i' is not very meaningful. Let's do some more refactoring? public static int[] factor(final int i) { return new int[] {i}; } 11) What is the next test that we should add? public static int[] factor(int multiple) { // Compute the new factors: int currentFactor = 0; int[] factorRegister = new int[2]; for (; (multiple % 2) == 0; multiple /= 2) { factorRegister[currentFactor++] = 2; } if (multiple != 1) { factorRegister[currentFactor++] = multiple; } // Trim the array, so that we pass the tests: int[] factors = new int[currentFactor]; for (int i = 0; i < currentFactor; i++) { factors[i] = factorRegister[i]; } return factors; } 12) Time to refactor again. The code is messy. What are the problems? - Many concepts in the same function. 13) How to improve it? package com.primeFactorizer; import java.util.Vector; public final class PrimeFactorizer { private static int factorIndex; private static int[] factorRegister; private PrimeFactorizer() { } public static int[] factor(int multiple) { initialize(); findPrimeFactors(multiple); return copyToResult(); } private static void initialize() { factorIndex = 0; factorRegister = new int[2]; } private static void findPrimeFactors(final int multiple) { int aux = multiple; for (; (aux % 2) == 0; aux /= 2) { factorRegister[factorIndex++] = 2; } if (aux != 1) { factorRegister[factorIndex++] = aux; } } private static int[] copyToResult() { int[] factors = new int[factorIndex]; for (int i = 0; i < factorIndex; i++) { factors[i] = factorRegister[i]; } return factors; } } 14) What is the next test case? public final void testFive() throws Exception { int[] factors = PrimeFactorizer.factor(5); assertEquals(1, factors.length); assertEquals(5, factors[0]); } 14.1) Do we have to change the code to pass 5? 15) What is the next test case? public final void testSix() throws Exception { int[] factors = PrimeFactorizer.factor(6); assertEquals(2, factors.length); assertEquals(2, factors[0]); assertEquals(3, factors[1]); } 16) And the next? public final void testSeven() throws Exception { int[] factors = PrimeFactorizer.factor(7); assertEquals(1, factors.length); assertEquals(7, factors[0]); } 17) What is the next test case that will fail? public final void testEight() throws Exception { int[] factors = PrimeFactorizer.factor(8); assertEquals(3, factors.length); assertEquals(2, factors[0]); assertEquals(2, factors[1]); assertEquals(2, factors[2]); } 18) How to make it pass? 18.1) We must increase the size of the array of factors, but, which size to use? 18.2) What is the largest number of factors an integer can have? 18.3) What is the largest integer that we can handle? public static final int MAX_NUM_FACTORS = 64; private static void initialize() { factorIndex = 0; factorRegister = new int[MAX_NUM_FACTORS]; } 19) What is the next case that will fail? public final void testNine() throws Exception { int[] factors = PrimeFactorizer.factor(9); assertEquals(2, factors.length); assertEquals(3, factors[0]); assertEquals(3, factors[1]); } 20) What is the most general algorithm that will make this test pass too? private static void findPrimeFactors(final int multiple) { int aux = multiple; for (; (aux % 2) == 0; aux /= 2) { factorRegister[factorIndex++] = 2; } for (; (aux % 3) == 0; aux /= 3) { factorRegister[factorIndex++] = 3; } if (aux != 1) { factorRegister[factorIndex++] = aux; } } 21) How this would extends to 25? 22) Can you see the loop pattern? private static void findPrimeFactors(final int multiple) { int aux = multiple; for (int factor = 2; aux != 1; factor++) { for (; (aux % factor) == 0; aux /= factor) { factorRegister[factorIndex++] = factor; } } } 23) What is the next test case that fails? 24) Code a bigger test: public final void testBig() throws Exception { int num = 2 * 3 * 5 * 7 * 11 * 13; int[] knownFacts = {2, 3, 5, 7, 11, 13}; int[] factors = PrimeFactorizer.factor(num); assertEquals(6, factors.length); assertEquals(2, factors[0]); for (int i = 0; i < factors.length; i++) { assertEquals(knownFacts[i], factors[i]); } } 15) How to generate Javadocs for this project? - click Project. - click Generate JavaDocs. - select only this project files that you want. 16) How to document a program? Software documentation - enforced by the compiler - Types (including qualifiers) - Post/Pre-conditions - not-enforced: - comments - Format is embedded in the language. - Format is free. - coding standards - Naming - patterns 17) Which kind of comments can we expect to find? - Interface comments - Classes - Methods - Internal comments - Method implementation 18) What should be documented? - Classes - The class responsibility - Known bugs - Patterns used - Anything that goes against the expected behavior, i.e, for (int = 1; i < N; i++) { ... } // We use 1 because ... - Methods - Purpose - Usage examples - Known bugs - Algorithm used - Anything that goes against the expected behavior 19) Any problem with the excess of comments? - Sync - Visual pollution Case study: bit counter ======================= % Write a class BitManipulator, that, counts the number of bits in a given number. % 20) What is the simplest test, with answer? public final void testZero() throws Exception { int numSet = BitManipulator.countNumSet(0L); assertEquals(numSet, 0); } public static byte countNumSet(final long l) { byte acc = 0; return acc; } 21) The next test + answer? public final void testOne() throws Exception { int numSet = BitManipulator.countNumSet(1L); assertEquals(numSet, 1); } -- public static byte countNumSet(final long l) { if (l == 0) return 0; else return 1; - or - return l = 0 ? 0 else 1; } 22) Can you make it more general? public static byte countNumSet(final long l) { return (byte) (l & 1L); } 23) Next test that fails? public final void testTwo() throws Exception { int numSet = BitManipulator.countNumSet(2L); assertEquals(numSet, 1); } public static byte countNumSet(final long l) { byte acc = 0; acc += ((l & 1L) == 1L) ? 1 : 0; acc += ((l & 2L) == 2L) ? 1 : 0; return acc; } 24) Is test three going to pass? public final void testThree() throws Exception { int numSet = BitManipulator.countNumSet(3L); assertEquals(numSet, 2); } 25) What is the next test that fails? public final void testFour() throws Exception { int numSet = BitManipulator.countNumSet(4L); assertEquals(numSet, 1); } public static byte countNumSet(final long l) { byte acc = 0; acc += ((l & 1L) == 1L) ? 1 : 0; acc += ((l & 2L) == 2L) ? 1 : 0; acc += ((l & 4L) == 4L) ? 1 : 0; return acc; } 26) What is the next test that fails? 27) Can you see the loop pattern? public static byte countNumSet(final long l) { byte acc = 0; for (long mask = 1; mask != 0; mask <<= 1) { acc += ((l & mask) == mask) ? 1 : 0; } return acc; } 28) Let's devise some more general tests? public final void testUnderEight() throws Exception { assertEquals(BitManipulator.countNumSet(5L), 2); assertEquals(BitManipulator.countNumSet(6L), 2); assertEquals(BitManipulator.countNumSet(7L), 3); } 29) What about negative numbers? public final void testNegative() throws Exception { assertEquals(BitManipulator.countNumSet(-1L), 64); assertEquals(BitManipulator.countNumSet(-2L), 63); assertEquals(BitManipulator.countNumSet(-3L), 63); assertEquals(BitManipulator.countNumSet(-4L), 62); }