תבניות מחשבה ושפות תכנות

11/04/2018

אני קורא עכשיו את הספר Refactoring ובאחת הדוגמאות שם מרטין פולר מביא קטע קוד Java שנראה דומה לזה:

public class a {
  public static String checkSecurity(String [] people) {
    String found = "";
    int i=0;
    while (i < people.length && found.equals("")) {
      if (people[i].equals("Don")) {
        // report Don
        found = "Don";
      }
      if (people[i].equals("John")) {
        // report John
        found = "John";
      }
      i += 1;
    }
    return found;
  }

  public static void main(String [] args) {
    String res = checkSecurity(args);
    System.out.println(String.join(", ", args));
    if (res != "") {
      System.out.println("Found intruder: " + res);
    }
  }
}

כדי לשפר את הקוד ב Java הוא מוציא חלקים מהקוד לפונקציות ובוחר שמות מדויקים יותר כך שבסוף הקוד באמת נראה קצת יותר מובן. הספר מבוסס על Java 2 וזו לדעתי הסיבה שלא הופיע בו הפיתרון המתבקש: שימוש ב Lambda Expressions.

ביטויי למדא הם פיצ׳ר של Java 8 שמאפשר הרבה יותר בקלות להפריד בין התנאי (האם השם הוא John או Don) לבין הלולאה שעוברת על המחרוזות ומחפשת את השם החשוד. בגירסאות חדשות של Java הרבה יותר הגיוני לכתוב:

interface StringPredicate {
  boolean check(String candidate);
}

public class b {
  public static String find_first(String [] people, StringPredicate p) {
    for (int i=0; i < people.length; i++) {
      if (p.check(people[i])) {
        return people[i];
      }
    }
    return null;
  }

  public static String checkSecurity(String [] people) {
    return find_first(people, (name) -> (name.equals("John") || name.equals("Don")));
  }

  public static void main(String [] args) {
    String res = checkSecurity(args);
    System.out.println(String.join(", ", args));
    if (res != "") {
      System.out.println("Found intruder: " + res);
    }
  }
}

עכשיו אפשר להשתמש ב find_first בכל מקום שנרצה למצוא את האיבר הראשון במערך של מחרוזות שמקיים תנאי מסוים. בהקשר הזה מעניין להשוות ל Ruby:

def find_first(items)
  items.each do |item|
    break item if yield(item)
  end
end

found = find_first(ARGV) { |name| %w[John Don].include?(name) }
puts "Found Intruder: #{found}"

הקוד ברובי שקול (והרבה יותר קצר), אבל מה שחשוב שהוא גנרי. בעוד ש find_first של Java התעקשה לקבל פרדיקט של מחרוזות, ברובי קל להעביר פרדיקט כלשהו. לפני שאתם קופצים להמליץ לי על Generics שווה להזכיר שביטויי למדה לא עובדים עם פונקציות גנריות.

מתכנתי Ruby רגילים לתבנית של פונקציה שמקבלת Callback (נקרא ברובי בלוק) בגלל שהשפה מעודדת אותנו להשתמש במבנה זה. מתכנתי Java רגילים למבנים אחרים ומתכנתי JavaScript למבנים נוספים. וכאן אנחנו מגיעים לקושי במעבר בין שפות: לא מספיק לחפש איך לעשות את הדבר שאתם יודעים בשפה החדשה. יותר חשוב לחפש את התבניות שאתם לא רגילים לכתוב כי הן לא פופולריות בשפה הקודמת שלכם.