Beautiful Methods

though I make no claim about Beautiful Slides :)

Beautiful Code:
Code that
reads like prose

Why we need it?

Missing deadlines...

Sometimes we need to finish, fix or extend code that someone else wrote!

How we see code from others

Even if we don't want
to deal with other
people's code,
sometimes it's unavoidable 

Developers get sick

Move to another company

They take vacations

Or can't remember what they did

"It would take less time to rebuilt everything"

It doesn't need
to be like that!

Clean Code:
Code that developers can understand without the author's explication

Beautiful Code:
Anyone Can Understand it

Clean Code hasn't
been enough! We need
Beautiful Code!

Code is the foundation

The fastest we move into Containers, Big Data, AI, Blockchains, etc. the more important it is to write Beautiful Code

What's the worst that can happen?

What's the worst that can happen?

How can I write beautifully?

Methods are the key

A Method is not
related with reuse

A Method gives
a name to
an algorithm

if (n % 2 == 0) { // even
  // code
}
if (n % 2 != 0) { // odd
  // code
}

Clean but not Beautiful

if (n % 2 == 1) {
  // wrong :(
}
if (n % 2 != 1) {
  // wrong :(
}
if (n % 1 == 0) {
  // wrong :(
}

Don't confuse
simple with familiar

boolean isMultiple(int a, int b) {
   return a % b == 0;
}

boolean isEven(int n) {
   return isMultiple(n, 2);
}

boolean isOdd(int n) {
   return !isEven(n);
}

Beautiful Code

boolean isLeapYear(int year) {
  return isMultiple(year, 4) && 
          (isMultiple(year, 400) || 
                  !isMultiple(year, 100));
}

Beautiful Code

boolean isLeapYear(int year) {
  return year % 4 == 0 && 
          (year % 400 == 0 || 
                  year % 100 != 0);
}

Not so Beautiful

Sieve of Eratosthenes

Prime Numbers

  1. Write all numbers greater or equal to 0
  2. Cross 0 and 1 because they aren't prime
  3. Move to the next uncrossed number
  4. Circle it, and cross its multiples
  5. Repeat step 3
public class PrimeChecker {

  public static final int MAX_PRIME = 1_000_000;
  private boolean[] sieve;

  public PrimeChecker() {
    this.sieve = new boolean[MAX_PRIME + 1];
    Arrays.fill(sieve, true);
    sieve[0] = false;
    sieve[1] = false;
    for (int i = 2; i * i <= MAX_PRIME; ++i) {
      if (sieve[i]) {
        for (int j = i * i; j <= MAX_PRIME; j += i) {
          sieve[j] = false;
        }
      }
    }
  }

  public boolean isPrime(int n) {
    return sieve[n];
  }
}
public class PrimeChecker {

  public PrimeChecker() {
    createSieve();
    uncrossAllNumbers();
    crossPrimesByDefinition();
    crossMultiplesOfKnownPrimes();
  }

  // code
}
private void crossPrimesByDefinition() {
  cross(0);
  cross(1);
}

private void crossMultiplesOfKnownPrimes() {
  for (int n = 2; hasAtLeastOneUncrossedMultiple(n); ++n) {
    if (isPrime(n)) {
      crossMultiplesOf(n);
    }
  }
}
  
private void crossMultiplesOf(int n) {
  for (int j = firstUncrossedMultipleOf(n); 
      j <= MAX_PRIME; nextMultiple(n, j)) {
    cross(j);
  }
}
private boolean hasAtLeastOneUncrossedMultiple(int n) {
  return firstUncrossedMultipleOf(n) <= MAX_PRIME;
}

private int firstUncrossedMultipleOf(int n) {
  return n * n; 
}

private int nextMultiple(int n, int x) {
   return n + x;
}
public boolean isPrime(int n) {
  return sieve[n];
}
  
private void createSieve() {
  this.sieve = new boolean[MAX_PRIME + 1];
}
  
private void uncrossAllNumbers() {
  Arrays.fill(sieve, true);
}
  
private void cross(int n) {
  sieve[n] = false;
}

Min Heap

Heap

Min Heap

  • A parent is always less than its children
public final class MinHeap {

  private final int[] elements;
  private int size;

  public MinHeap(final int[] numbers) {
    this.elements = new int[numbers.length];
    for (int i = 0; i < numbers.length; ++i) {
      this.elements[i] = numbers[i];
      ++size;
      swim();
    }
  }

  public int min() {
    int min = head();
    swap(0, size - 1);
    --size;
    sink();
    return min;
  }
  // code
}
private void swim() {
  int index = size - 1;
  while (hasParent(index) && greater(parent(index), index)) {
    swap(parent(index), index);
    index = parent(index);
  }
}

private void sink() {
  int index = 0;
  while (hasLeftChild(index)) {
    int minChild = minChild(index);
    if (greater(minChild, index)) {
      return;
    }
    swap(minChild, index);
    index = minChild;
  }
}
private int minChild(final int index) {
  if (hasRightChild(index)) {
    return less(leftChild(index), rightChild(index)) 
            ? leftChild(index) 
            : rightChild(index);
  }
  return leftChild(index);
}

private boolean hasParent(final int index) {
  return parent(index) >= 0;
}

private boolean hasLeftChild(final int index) {
  return leftChild(index) < size;
}

private boolean hasRightChild(final int index) {
  return rightChild(index) < size;
}
private int rightChild(final int index) {
  return leftChild(index) + 1;
}

private int parent(final int index) {
  return (index - 1) / 2;
}

private int leftChild(final int index) {
  return 2 * index + 1;
}
private int head() {
  return elements[0];
}

private void swap(final int i, final int j) {
  int temp = elements[i];
  elements[i] = elements[j];
  elements[j] = temp;
}

private boolean greater(final int i, final int j) {
  return elements[i] > elements[j];
}

private boolean less(final int i, final int j) {
  return elements[i] < elements[j];
}
public class Benchmark {

  public static void run() {
    long time = 0L;
    long sum = 0L;
    int total = 100_000;
    for (int times = 1; times <= total; ++times) {
      long startTime = System.nanoTime();
      int[] numbers = generate(1_000);
      MinHeap minHeap = new MinHeap(numbers);
      for (int n = 1; n <= numbers.length; ++n) {
        sum += minHeap.min();
      }
      long estimatedTime = System.nanoTime() - startTime;
      time += estimatedTime;
    }
    System.out.println(String.format("Execution averaged %f seconds", 
        time / (total * 1_000_000_000.0), sum));
  }

  private static int[] generate(int size) {
    Random random = new Random(0L);
    int[] numbers = new int[size];
    for (int i = 0; i < size; ++i) {
      numbers[i] = random.nextInt();
    }
    return numbers;
  }
}
Execution averaged 0.000068 seconds // Ugly
Execution averaged 0.000070 seconds // Beautiful

* on my computer,
not state-of-the-art benchmark,
difference is what's important

Performance

it has an impact, but...

Small Methods != Bad Performance

Conclusion

Beautiful Methods

  • Easier to reason
  • Easier to test
  • Easier to fix / extend
  • Better than just "Clean Code"

Beautiful Methods

  • Write them iteratively
  • Group related code into a method
  • Don't mix levels of abstractions
  • Don't worry, yet, about performance

Care about the
code you write!

Q&A

Thanks!

Beautiful Methods: Code that reads like prose

By Carlos Obregón

Beautiful Methods: Code that reads like prose

  • 2,010