תכנות מונחה-עצמים בשפת רובי

שפת רובי היא שפת Object-Oriented (בקיצור: OO).

כמו כל שפה, היא עושה זאת בדרך משלה, שהיא קצת שונה משפות אחרות. אני כותב את הפוסט הבא לקהל מפתחי ג\'אווה שמכיר OO היטב. אינני מנסה להסביר עקרונות של OO ואניח שאתם מכירים, למשל, את ההבדל בין extends ל implements.

את המפגש הראשון עם מחלקות ברובי כבר עשינו בפוסט הראשון בסדרה. אם שכחתם – קפצו לחלק שנקרא \"טבילת אש ראשונה\" בכדי להיזכר.

בפוסט זה אני רוצה לצלול יותר עמוק, הרבה יותר עמוק – ולהבין באמת את מודל ה OO של רובי. אנסה לענות על שאלות כגון:

  • היכן האובייקטים, המחלקות, הממשקים, וכלי ה OO שאנו מכירים מג\'אווה – נמצאים ברובי?
  • מהם מודולים, וכיצד משתמשים בהם?
  • מה ההבדל בין מודל ה OO של רובי לבין זה של ג\'אווה? (רמז: יש הבדלים רבים)
  • כיצד יודעים לחזות, ללא הפתעות – איזו מימוש של מתודה יופעל כאשר האובייקט שלנו הוא חלק משרשרת הורשה?
  • להכין אתכם בפני כמה מהפתעות אפשריות בשימוש ב super – כך שלא יהיו לכם הפתעות.

נפתח בהתמצאות בסיסית:

המבנים הבסיסיים של ה OO ברובי הם:

  • מחלקה (class)
  • אובייקט (object)
  • מודול (module) – אפשר לחשוב עליו כ\"מודול הרחבה\" שניתן \"לחבר\" אותו למחלקה בכדי להרחיב את היכולות שלה.
  • מחלקה יחידנית (singleton class) – לפעמים נקראת גם metaclass, משמשת להשגת התנהגויות מסויימות שנדבר עליהן בהמשך.
  • מבנה (struct) – סוג של \"תחליף זול\" ליצירת מחלקות פשוטות בקלות.

ברובי אין interfaces, ואין abstract classes.

בואו נשים לב לעוד כמה תכונות והבדלים בין המבנים שיכולים לעזור בהתמצאות:

  • ניתן לייצר מופעים (instances) ממחלקות וממבנים בלבד.
  • מודול ומחלקה יחידנית הם דומים מאוד למחלקה – אבל יש עליהם כמה מגבלות (הברורה: אין מתודה new ליצירת מופעים).
  • כל המבנים הנ\"ל הם בעצם גם אובייקטים לכל דבר: הם instances של מחלקה כלשהי, יש להם self, וכו\' – לא שונה כ\"כ מג\'אווה, למען האמת.

הערה: מכיוון ש\"כל המבנים הם אובייקטים\", אשתמש במונח \"מופע\" (instance) בכדי לתאר אובייקט שהוא מופע של מחלקה (class), כלומר: אובייקט \"פשוט\".

נראות במחלקה

מה העיקרון החשוב ביותר ב OO? – הכמסה!
בואו נראה איך הכמסה עובדת ברובי.

כברירת מחדל ברובי:

  • כל משתני המחלקה (@@) או משתני המופע (@) – הם private
  • כל המתודות – הן public
  • מקרה מיוחד היא המתודה initialize (הבנאי) שהוא private: המתודה new של המחלקה BasicClass (ממנה כל מחלקות הרובי יורשות) היא public והיא קוראת לבנאי של המחלקה שלנו.
יש שוני בין ההגדרות של private ו protected בין ג\'אווה (או ++C) לבין רובי.

נתחיל בהגדרת ה private ברובי (במילה: מחמיר מעט יותר מג\'אווה):

  • ברובי private הוא פרטי של המופע – כלומר מופעים אחרים של המחלקה לא יכולים לגשת אליו (כמו שהם יכולים בג\'אווה – אם אתם לא מכירים. בג\'אווה כל האובייקטים מאותו המחלקה הם חברים, friends)
  • ההגדרה ברובי ל private: לא ניתן לקרוא למשתנה / מתודה – אם צריך לציין מי המקבל של ההודעה.
  • להבהיר: הורים וילדים של המחלקה – יוכלו לגשת למתודה, אולם באופן עקיף – ע\"י הקריאה למתודה כאילו היא שלהם או ע\"י super. בן לא יוכל לקרוא למתודה של מופע אחר מאותה המחלקה, למשל.

למשל:

class MyClass

@other_object = MyClass.new

def foo
say_it
# specified no-one - okay!
self.say_it # specified \'self\' - no go.
@other_object.say_it # specified \'other_object\' - no go.
end

private

def say_it
puts
\'yay!\'
end

end

x = MyClass.new
x.say_it
# NoMethodError
x.foo # yay!, then NoMethodErrors
המילה השמורה private בגוף המחלקה מגדירה שכל מתודה שהוגדרה מקטע זה ואילך תהיה private (דומה ל ++C). ניתן בהמשך להגדיר מקטע protected ואז public או private בחזרה, וכו\'

למתכנת ג\'אווה מנוסה, הקוד הבא אמור להעלות שאלה:
\"ניתן לראות בקלות ש x.say_it היא קריאה למתודה פרטית. למה ה IDE (נניח RubyMine) צועק על בעיות אחרות – אך לא על זו ?!\"
התשובה היא בגלל שניתן לשנות את נראות המתודות בזמן ריצה – ולכן לא ניתן להחליט בבבירור שזוהי שגיאה.

ניתן לקרוא ל \"MyClass.protected :foo\" בזמן ריצה בכדי לשנות את המתודה (מתוך המחלקה) ל protected.
לא נהוג באמת לשנות את הנראות בזמן ריצה, אבל כן נהוג להשתמש במתודה הנ\"ל בכדי להגדיר שמית מתודות ואת הנראות שלהן – במקום לנהל את הנראות במקטעים.

מה המשמעות אם כן של protected?

protected ברובי היא דומה יותר ל private בג\'אווה. הההגדרה: המתודה היא נגישה כל עוד self מתייחס למופע של אותה המחלקה.
הקריאה יכולה להתבצע מהאובייקט עצמו, מופע אחר של אותה המחלקה, או מופע של מחלקה שיורשת / נורשת מאותה המחלקה. הייעוד של protected ברובי הוא לשתף מידע בין מופעים הקשורים זה-לזה.

מחלקות ומופעים – כיצד הם מיוצגים?

סה\"כ ניתן לראות את המחלקות של רובי כ\"שק של תכונות\" עליו ניתן להשפיע בצורה דינאמית, עם כמה מגבלות.

רובי מנהלת מבני נתונים למופעים ולמחלקות, כאשר:

  • מופעים מכילים state – משתנים
  • מחלקות מכילות מתודות וקבועים
  • מכיוון שמחלקה היא גם מופע – אזי היא גם מכילה משתנים, אם כי ב\"רמה\" אחרת.
מתודות המופע (instance methods)
מתודות המשותפות לכל המחלקות המופעים של המחלקה MyClass. אלו בעצם המתודות הרגילות של המחלקה, כמו המתודה foo בדוגמת הקוד למעלה.
מתודות המחלקה (class methods)
הן מתודות שזמינות להפעלה מתוך reference למחלקה עצמה. הן דומות למתודות סטטיות (static method) בג\'אווה.
שימו לב שבתיעוד מקובל לסמן מתודות מופע ב# ומתודות מחלקה ב::
class MyClass
def self.my_class_method
puts
\'first!\'
end

def MyClass.second_class_method
puts
\'second!\'
end

class << self
def MyClass.third_class_method
puts
\'third!\'
end
end
end

x = MyClass.new

MyClass.my_class_method # first!
MyClass.second_class_method # second!
MyClass.third_class_method # third!

x.my_class_method # error !@#$!
x.class.my_class_method # first!

הנה דוגמת קוד בה הגדרנו, בשלושה אופנים שונים, מתודות מחלקה.
הדרך השלישית נראית מעט מוזרה. מה שעשינו הוא קודם כל להחליף scope ל scope של ה singleton class של האובייקט MyClass (שהוא גם אובייקט לכל דבר) – ושם הגדרנו את המתודה. זאת מכיוון שמתודות מחלקה מוגדרות בעצם על ה singleton class של אובייקט המחלקה. מבלבל משהו.

מתוך המופע x אינני יכול לקרוא למתודות המחלקה של MyClass (למרות שאני מופע של MyClass), אלא רק בעזרת התייחסות (reference) למחלקה עצמה (למשל: x.class).

משתני מחלקה וקבועים
את משתני המחלקה אנו מכירים מהפוסט הראשון – הם מתחילים ב @@ והם דומים ל static fields בג\'אווה. ניתן לגשת אליהם מתוך מתודות של המופע.

נזכיר שגם משתני מופע (@) וגם משתני מחלקה (@@) הם private members. ניתן לגשת אליהם מתוך מתודות של המחלקה / מופע – אך לא מתוך קריאה ל x.@some_variable (מדוע? מכיוון שציינו את מקבל ההודעה, כמובן!)

class MyClass
@@y = 4
Z = @@y

def foo
puts
@@y
@@y += 1
end
end

MyClass.new.foo # first instance -> 4
MyClass.new.foo # second instance -> 5
# MyClass.new.@@y = syntax error

x = MyClass.new
puts
MyClass::Z # 4
puts x::Z # error!

אופיים של קבועים (Z – במקרה שלנו) נקבע ע\"י כך ששמם שמתחיל באות גדולה. שימו לב שמחלקות ומודולים – הם (אובייקטים) קבועים בשפת רובי.

מתודות ישירות / יחידניות (singleton method)
בשונה מג\'אווה ניתן להגדיר מתודות על מופע בודד – ולא על המחלקה.

class MyClass
def foo
puts
\'yay!\'
end
end

y = MyClass.new

def y.goo
puts
\'woo\'
end

x = MyClass.new

y.goo
# woo
puts y.singleton_class.method_defined? :goo # true

puts x.singleton_class.method_defined? :goo # false
x.goo # NoMethodError

כלומר: המתודה goo קיימת רק על המופע y – ולא על שאר המופעים במחלקה.
אם אתם זוכרים את הכללים שלמעלה – מופע (אובייקט) מכיל רק state ולא מתודות. מתודות יושבות על מחלקות או מודולים. כיצד זה מסתדר עם מה הקוד שזה עתה ראינו?

ובכן… ברגע שאנו מגדירים מתודה על \"מופע\", בעצם מאחורי הקלעים נוצרת מחלקה יחידנית (singleton class), חסרת שם, שתארח את המתודה הזו. המופע יחזיק התייחסות (reference) למחלקה היחידנית – יכולה להיות לו אחת כזו, לכל היותר.

באופן דומה, גם מתודות מחלקה שראינו קודם לכן הן בעצם \"מתודות ישירות\" על אובייקט המחלקה – ומאוכסנות במחלקה היחידנית של המחלקה. מבלבל?

המחלקה היחידנית של MyClass היא לא חלק מהיררכיית ההורשה של המופע x (היררכית הורשה היא עניין אישי ברובי) – ולכן אם מתודת המחלקה נקראת ישירות מתוך המופע (x.some_class_method) – המופע לא ימצא אותה.

הנה דרך נוספת להגדיר singleton method על המחלקה x

x = MyClass.new
y
= MyClass.new

class << x
def poo
puts
\'ok\'
end
end

puts x.poo # ok
puts y.poo # NoMethodError

הביטוי class << x גורם לנו להכנס ל scope של מהחלקה היחידנית. פשוט.
בכדי שלא יהיה משמעם, קיים הבדל בין שתי דרכי ההגדרה של מתודה על מחלקה יחידנית – הבדל שקשור לנראות של קבועים על המחלקה היחידנית. בצורת ההגדרה הקודמת  הפונקציה לא \"תראה\" את הקבועים שעל המחלקה היחידנית, ואילו בצורת ההגדרה הנוכחית – הם יהיו זמינים מחלקה. למה? מדוע? – לא חפרתי מספיק בכדי להבין…

הערה אחרונה בנושא: הגדרת נראות private למתודת מחלקה (class method) נעשית בצורה מעט שונה: לא ע\"י שימוש ב private שאנו משתמשים עבור מתודות מופע, אלא ע\"י שימוש ב private_class_method. שימוש מקובל ב directive הזה הוא להחביא את המתודה new:: בכדי להגדיר דפוס עיצוב של Singleton (של GOF).
ה directives של הנראות (private, public, protected) הן בעצם פונקציות של המחלקה Module המשפיעות על המטא-מודל של האובייקטים ברובי. מכיוון שמתודות מחלקה לא נמצאות באמת על המחלקה (אלא על המחלקה היחידנית של המחלקה) – זקוקים למנגנון מעט שונה בכדי לשנות את הנראות שלהם מבלי לסבך. מבלי לסבך את המפשן של רובי, התכוונתי.

זוכרים שהזכרנו בתחילת הקטע את המטאפורה של המחלקה כ\"שק של תכונות\"?
בואו נראה התנהגות זו בפעולה:

class MyClass
def foo
puts
\'aleph\'
end

def foo
puts
\'beth\'
end
end

x = MyClass.new
x.foo
# beth

הגדרנו מתודה בשם foo, ואז הגדרנו אותה שוב.
התוצאה? דריסה של רישום המתודה הראשון ברישום השנה – וזה מה שנשאר.

בשפת רובי אין overloading. אם נגדיר שתי מתודות עם אותו השם, אבל חתימה שונה של מספר הפרמטרים – המתודה שכתובה מאוחר יותר תדרוס את זו הקודמת לה, ללא קשר למספר הפרמטרים. ברובי, השם הוא המזהה היחידי למתודה –  ולא ערך החזרה (הוא תמיד אובייקט) או מספר הפרמטרים.

הנה דוגמה נוספת:

class MyClass
def foo
puts
\'aleph\'
end
end

x = MyClass.new

class MyClass
def goo
puts
\'beth\'
end
end

y = MyClass.new
x.foo
# aleph
x.goo # beth
y.foo # aleph
y.goo # beth

הגדרנו את המחלקה MyClass ואז הגדרנו אותה שוב?
הפעולה הזו נקראת ברובי \"re-opening a class\". הגדרה נוספת של מחלקה לא \"דורסת\" את רישום המחלקה הקיים, אלא גורמת לרישום חדש או נוסף של כל ה members לאותו \"שק\" של מתודות שנקרא MyClass.

הרעיון של פיצול של ההגדרה של מחלקה למספר מיקומים (קבצים שונים, אולי חלקם נטען לתוכנה מסויימת – בעוד האחרים לא) הוא מן גמישות גבוהה ובעייתית מאוד.

עצם העניין של מחלקה הוא לרכז משימה יחידהבמסגרת יחידה. זוהי מהות היישות הזו שנקראת מחלקה.
הרעיון לפרק את המסגרת הזו לחלקים, ולפזר אותה – לא מסתדר עם הרעיונות הבסיסיים ביותר שעומדים מאחורי OO.

ברובי ניתן, מכל מקום בקוד, לפתוח מחלקה ולהרחיב אותה. למשל – להוסיף מתודה למחלקה Integer.
אני מוצא גמישות זו כמועילה מעט (אולי זה יכול להיות פתרון אלגנטי לכמה מקרי-קצה) מצד אחד, ובעלת פוטנציאל נזק גדול למדי (מתכנתים מסיקים ש\"נכון\" ברובי לפצל מחלקות לכל מיני אזורים, מכל מיני סיבות) – מצד שני.

האם יש יכולת דומה בשפות אחרות?
בג\'אווהסקריפט ניתן לעשות אותו הדבר – שינוי של ה prototype של האובייקט (= בערך מחלקה ברובי) מכל מקום במערכת, וזה נחשבת פרקטיקה לא טובה.
ב #C יש Partial Classes שזה סוג של פיצול מחלקה לכמה קבצים שונים – אבל השימוש הנפוץ הוא שחלק אחד נוצר מ code generation והשני – מתוחזק בצורה ידנית, והם יושבים במבנה הפרוייקט זה לצד זה.

בקיצור:ע\"פ כל קריטריטיון שאני מכיר, כדאי להמנע מלהשתמש ביכולת ה\"פתיחה מחדש של המחלקה\" – ולשמור על יכולת ההתמצאות (orientation) וההבנה הקלה של המערכת. שימוש אחד שיכול להיות סביר להרחבת מופעים והוספת singleton_methods היא unit-testing ו mocking. עדיף קוד שלא דורש זאת – אך יש כמה מצבים שבהם אני מעריך שהייתי שמח להשתמש ביכולות הללו בבדיקות.

מודולים

מודול ברובי הוא מבנה שמקבץ מתודות, שבניגוד למחלקה – אי אפשר לייצר ממנו מופע (instance).
איך משתמשים במתודות הללו? \"מחברים\" את המודול למחלקה (או כמה מחלקות) – והן יקבלו את הפונקציונליות הנוספת. מודולים הם בעצם, מה שנקרא בשפות אחרות mixin.

module NicePrinter
def putz(msg)
puts
\'[\' + msg + \']\'
end
end

class
MyClass
include NicePrinter

def fooz
putz
\'yay!\'
end
end

MyClass.new.fooz # [yay!]

ניתן לשלב מודול בכמה מחלקות, ולבצע include לכמה מודולים באותה המחלקה (קירוב של \"הורשה מרובה\").

דרך נוספת לשלב מודול במחלקה הוא בעזרת extends

module NicePrinter
A = 1
def putz(msg)
puts
\'[\' + msg + \']\'
end

module NiceSpacer
B = 2
def add_spaces(str)
str.scan(
/./).join(\' \')
end
end
end

class
MyClass
include NicePrinter
extend NicePrinter::NiceSpacer

def fooz
putz
MyClass.add_spaces(\'yay!\')
end
end

x = MyClass.new

x.fooz
# [y a y !]
puts MyClass::A # 1
puts MyClass.singleton_class::B # 2
puts MyClass::NiceSpacer::B # 2

בדוגמה הזו עשינו include כמו בדוגמה הקודמת, וגם עשינו extend ל מודול המקונן NiceSpacer (ניתן לעשות extends לכל מודול – פשוט רציתי להראות קינון של מודולים \"על הדרך\").

למה השתמשנו ב\" ::\" ולא פשוט בנקודה? כי :: הוא ה resolution directive – דרכו מגיעים לקבועי-מחלקה. מודול (מכיוון ששמו מתחיל / חייב להתחיל באות גדולה) הוא קבוע,  – ועל כן זו הדרך הנכונה לגשת אליו. שימוש בנקודה היה זורק Exception.

extend, בניגוד ל include, מוסיף את המתודות שעל המודול להיות class methods. להזכיר: include מוסיף את המתודות שעל המודול להיות instance methods. כנ\"ל לגבי קבועים.

שימו לב שהקבוע B נוסף לנו פעמיים:

  • בפעם הראשונה (כרונולוגית) – כחלק מה include: כאשר עשינו include נוספף המודול NicePrinter וכל המודולים המקוננים שלו (במקרה שלנו: NiceSpacer).
  • בפעם השניה פעם על המחלקה היחידנית של MyClass – כאשר השתמשנו ב extends

 ברגע שמוסיפים מודול מקונן – אז גם המודול הפנימי נוסף תחת ה namespace המקונן שלו. בדוגמה למעלה בעצם הוספנו את NiceSpacer פעמיים: פעם כמודול מקונן על המחלקה MyClass, ופעם ישירות על המחלקה היחידנית של MyClass. אני מקווה שקריאת ההסבר פעמיים תספיק בכדי לקלוט את העניין… אם לא – פשוט פתחו irb ונסו קצת בעצמכם.

האם יש למודולים עוד תכונות ויכולות? – כן.

ניתן להגדיר צורות שונות של תלויות בין מודולים כך שחיבור של אחד למחלקה (ע\"י include או exclude) בעצם יחבר גם מודולים אחרים, או סתם \"יפתח\" (re-open) את המחלקה שמוסיפה את המודול ויבצע בה שינויים.

יכולות אלו שימושיות מאוד לבניית DSL – אבל כדאי מאוד להיזהר בהן בכתיבת \"תוכנה בסיסית\". לכו תבינו שמתודה שלכם מתנהגת אחרת כי מודול שנוסף, גרר מודול אחר – שעושה שינוי ב members של המחלקה…
התחכמות (cleverness) ב Metraprogramming של רובי היא סגולה ללילות לבנים ללא שינה. ראו הוזהרתם!

מודולים משמשים גם כ namespace (כמו ב ++C או #C), ניתן לאגד בתוכם מחלקות, פונקציות, וקבועים – תחת שם שלא יתנגש עם מחלקות, פונקציות, וקבועים אחרים:

module MyModule
class MyOtherClass
def goo
puts
\'wow!\'
end
end
end

class
MyClass
include MyModule
end

x = MyModule::MyOtherClass.new
x.goo
# wow!

y = MyClass::MyOtherClass.new
y.goo
# wow!

האם ניתן לעשות include ו/או extend גם למודולים כאלו שמכילים מחלקות? – בוודאי. חשבו על Module ומחלקה כ hash (\"שק תכונות\") שניתן להרכיב אותם (עם כמה מגבלות) אחד על השני.

בדוגמת הקוד ניגשתי פעם ל MyOtherClass דרך המודול כ Namespace, ופעם אחרת כקבוע על המחלקה MyClass. שתי הדרכים אפשריות ותקינות.

מתודות ו\"שליחת הודעות\"

בשונה משפות כמו ג\'אווה / ++C בהם מתייחסים לx.foo כ \"method invocation\", ברובי (כמו ב Smalltalk או Objective-C) מתייחסים להפעלת מתודות כשליחת הודעות.

למשל:

class MyClass
def foo
puts
\'yay!\'
end
end

x = MyClass.new

x.foo
# \'yay!\'
x.send :foo # \'yay!\'

קראנו ל foo ב 2 אופנים:

  • x.foo היא רמת ההפשטה הגבוהה, \"כאילו\" foo היא תכונה של המופע x.
  • x.send :foo היא האופן בו הדברים קורים בפועל ברובי – שליחת הודעה בשם foo ל x.

ההודעות הן סינכרוניות, כך שהפונקציה השולחת לא תוכל להמשיך לפני שחזרה תשובה.
אם כך, מהו בעצם ההבדל? האם יש פה הבדל עקרוני או התעקשות על מינוח שונה – אך חסר משמעות?

הנה כמה הבדלים עקרוניים:

  • ניתן לשלוח כל הודעה (בעזרת המתודה send) שראינו למעלה. זהו בעצם syntactic sugar למה שמתרחש באמת.
  • ניתן להענות לכל הודעה, גם ל\"הפעלת\" מתודה שלא הוגדרה מראש במחלקה – ע\"י מימוש המתודה method_missing במחלקה.
  • בעזרת method_missing ניתן לעכב הודעות, להתעלם מהודעות, וכו\'.

    כאשר קוראים למתודה שולחים הודעה למופע (אובייקט), מחפשים את מתודה בשם שנשלח בהודעה על האובייקט, ואם מתודה כזו לא נמצאה – מתחילים לחפש, בצורה רקרוסיבית, במעלה שרשרת ההורשה של המחלקה עד שמגיעים ל BasicObject (האב הקדמון ביותר).

    אם לא מוצאים את המתודה גם שם – אז מתחילים מהתחלה, אבל הפעם מחפשים אחר מתודהה בשם method_missing כאשר שם המתודה לה קראנו במקור הוא הפרמטר (+ רשימת הארגומנטים המקורית, כמובן).

    הכלי של method_missing מאפשר כח רב ברובי – להענות למתודות שלא הוגדרו מראש על ידי המחלקה.
    מימוש ברירת המחדל של method_missing הוא לזרוק exception, והוא נמצא בתוך module בשם kernel ש\"מחובר\" למחלקה Object.

    האם שתי דרכי הפעולה זהות?
    לא. אם תקחו את הדוגמה למעלה ותהפכו את foo למתודה private – תראו שדרך ההפעלה הראשונה (בעזרת נקודה) – נכשלת, אבל הדרך השניה (send) מצליחה. מדוע?

    אכיפת ה visibility ברובי נעשית כאשר משתמשים במנגנון הנקודה, ו send פשוט עוקפת את המנגנון הזה. send היא בעצם מתודה public של המחלקה Object (הבת הישירה של BasicObject) – שתפעיל את send_internal של המפרשן של רובי.
    כלומר: ע\"י send ניתן לקרוא, מכל מקום בקוד, למתודות private של מחלקות אחרות. מה עוצר מבעדנו לכתוב קוד שקורא ל private members של מחלקות אחרות ללא מגבלה? – משמעת עצמית בלבד.

    היררכיות ההורשה ברובי

    הנושא הזה הוא בעצם הנושא ממנו התחלתיאת הפוסט, כאשר הוספתי עוד ועוד חומר רקע (חשוב בפני עצמו) בכדי שאוכל לדון בנושא בחופשיות.

    • ברובי יש מנגנון של הורשה (inheritance) בין מחלקות – וזו הורשה יחידה.
    • ברובי, כפי שראינו, יש מנגנון של ה Modules שהוא מנגנון של mixins.
    • ראינו שיש גם מחלקות יחידניות (singleton classes) שהן אלמנט טכני, \"מאחורי הקלעים\", אבל הידיעה אודותיהן עוזר להבין כמה מההתנהגויות של רובי – שאחרת היה קשה להבין.

    הנה דוגמה פשוטה של הורשה:

    class Person
    def say_my_name
    puts
    \'john smith\'
    end
    end

    class
    Employee < Person

    end

    Employee.new.say_my_name # john smith

    אין פה שום דבר מפתיע.

    בואו נסבך מעט. האם אתם יכולים להסביר את התוצאה הבאה?

    module SongsWeLike
    def say_my_name
    puts
    \"destiny\'s child\"
    end
    end

    class
    Person
    include SongsWeLike
    end

    class
    Employee < Person

    end

    Employee.new.say_my_name # destiny\'s child

    puts Employee # Employee
    puts Employee.superclass # Person
    puts Employee.superclass.superclass # Object

    מצד אחד, התוצאה אינטואטיבית. זה מה שהיינו מצפים שתהיה ההתנהגות.
    מצד שני, קשה להסביר אותה – בהתבסס על \"טיול על עץ ההיררכיה\", מכיוון שהמודול הוא לא חלק מההיררכיה – נכון?

    הדברים מסתדרים כאשר לומדים את העיקרון הבא:
    כאשר מחברים מודול למחלקה, המפרשן של רובי בעצם יוצר מחלקה יחידנית ו\"דוחף אותה\" לתוך היררכיות ההורשה – מעל למחלקה עצמה. המחלקה היחידנית היא חסרת שם, ו\"בלתי נראית\" – אבל היא שם. היא מקושרת למודול ש\"חיברנו\", כך שמתודות המופע והקבועים משותפים לה ולמודול.

    בדוגמה למעלה, כאשר Person חיבר את המודול SongsWeLike, המפרשן יצר מחלקה יחידנית ורשם אותה כ superclass של המחלקה Person.

    מדוע לא הצלחנו (שורה אחרונה בקוד) לראות זאת? כי המפרשן של רובי מנסה להחביא את מה שהוא עשה. לא בכדי \"לבלבל את המתכנתים\", חלילה, אלא בכדי \"לפשט את המורכבות ולא לסבך אותם\". השלפן (getter) שנקרא superclass פשוט מדלג מעל מחלקות אנונימיות [א].

    הנה האופן שבו נראית ההיררכיה באמת:

    אם נפלט לכם עכשיו במקרה \"?WTF\" – זה בסדר. זו באמת היררכיה גדולה, הכוללת כמה אלמנטים טכניים. נסביר את הכללים:

    • במידה ויש למחלקה שלנו מחלקה יחידנית משלה (נניח שהגדרנו מתודה ישירות על המופע) – היא תכנס להיררכיה מעל המופע.
    • כפי שאמרנו כבר: כל מודול אותו מחברים למחלקה – מוסיף מחלקה יחידנית מעל המחלקה שהוסיפה אותו. המחלקה היחידנית מחוברת למודול וחושפת את המתודות / קבועים שלו.
      • משמעות ראשונה של המבנה הזה הוא שמודול לעולם לא יוכל \"להסתיר\" מתודות של המחלקה עם אותו השם: כיוון החיפוש אחר מתודה הוא מלמטה למעלה – תמיד קודם ניתקל במחלקה ורק אז במודולים שהיא הוסיפה.
      • אם מוסיפים למחלקה כמה מודולים – אזי המחלקות היחידניות של המודולים יתווספו להיררכיה בסדר הפוך לסדר החיבור שלהם למחלקה, קרי LIFO. 
    • המבנה הזה אולי נראה גדול ומורכב – אבל הוא מפשט את ה runtime של רובי: בעת קריאה למתודה, המפרשן פשוט צריך לטייל על ההיררכיה מלמטה למעלה – בלי \"לחשוב הרבה\". פעולה של חיפוש אחר מתודה מתרחשת תכופות בזמן ריצה – ולכן חשוב מאוד שהיא תתבצע במהירות.

    super

    מה קורה כאשר אנו רוצים להרחיב מחלקת-אב, אך עדיין להשתמש בחלק מהפונקציונליות שלה?
    בדומה לג\'אווה (שיש את this), יש ברובי מילה שמורה בשם super.

    class MySuperClass
    def foo(num)
    puts
    \'super \' + num.to_s
    end
    end

    class
    MyClass < MySuperClass
    def foo(n)
    puts
    \'duper:\'
    super
    super n + 1
    end
    end

    MyClass.new.foo 7 # duper:, super 7, super 8

    בניגוד לג\'אווה אין חובה ש super תהיה הקריאה הראשונה במתודה, ואין מניעה להשתמש בה רק פעם אחת.
    אם לא נציין פרמטרים – רובי באופן אוטומטי תשלח את כל הפרמטרים של הפונקציה שלנו – לפנוקציית ה super. אם מתודת ה super מצפה למספר שונה של פרמטרים – נקבל שגיאת ArgumentError.

    הערך של הפרמטרים שיעברו לסופר, הוא הערך הנוכחי שלהם – ולא הערך שהתקבל בהפעלת הפונקציה. כלומר: אם קיבלתי פרמטר, שיניתי אותו, ואז קראתי לסופר – סופר יקבל את ערך הפרמטר לאחר השינוי:

    class MySuperClass
    def foo(num)
    puts
    \'super \' + num.to_s
    end
    end

    class
    MyClass < MySuperClass
    def foo(n)
    n
    = 0
    super
    super n + 1
    end
    end

    MyClass.new.foo 7 # super 0, super 1

    אני מניח שהשיקול התכנוני מאחורי התנהגות זו הוא צמצום הגודל של ה local table (המקבילה ברובי ל Activation Frame של ++C) – תחת ההנחה שמדובר במקרה קצה שלא יבלבל הרבה מפתחי רובי.

    אם אתם רוצים לקרוא ל super ולא להעביר אף פרמטר –  עליכם לקרוא לסופר עם מרכאות ריקות, כלומר: ()super.
    זו התהגות מעט לא צפויה – כי סוגריים על מתודות הן רשות. בכל מקום אחר בשפה שימוש או ויתור על הסוגריים לא ישנה דבר – אך כאן יש להם משמעות מיוחדת.

    נזכיר, למען ההגינות, ש super היא לא מתודה. היא רק מילה שמורה שנראית \"כמו\" מתודה. כמו כן, אני לא חושב שיש לי פתרון אלגנטי יותר לבעיה. המשמעות המיוחדת של סוגריים בקריאה ל super היא פשוט התנהגות לא-צפוייה שכדאי להיות מודעים אליה.

    האם סיימנו עם ההפתעות? – חס וחלילה!

    ניתן לחשוב בתמימות ש super תמיד קורא למתודה של ה superclass עם אותו השם. זה הגיוני, אבל אם אתם זוכרים ההיררכיה יכולה להכיל מחלקות יחידניות – מה שעלול להוביל להתנהגות לא צפוייה ו/או לא רצויה.
    במקום זאת, בקריאה ל super רובי תפעיל את אלגוריתם החיפוש אחר מתודה עם השם של המתודה ממנה היא הופעלה – החל מה superclass של המחלקה הנוכחית ומעלה. כלומר: תעלה לאורך שרשרת ההורשה עד שהיא מוצאת מתודה בשם של המתודה ממנה היא נקראה. אם היא הגיעה ל BasicObject ולא מצאה מתודה בשם הצפוי – היא או תתחיל לחפש (שוב – החל מה superclass שלנו) מתודה בשם method_missing.

    מצד אחד – זה הגיוני, מצד שני – עלול להפתיע.

    משמעות מעניינת אחת של ההתנהגות הזו היא שאפשר להשתמש ב super בכדי להגיע למתודה במודול שחיברנו למחלקה – אך דרסנו לו מתודה באותו השם. כמובן שאם חיברנו כמה מודולים עם מתודות עם אותו השם – אנו נגיע רק למתודה של המודול שחובר ראשון.

    משמעות אחרת של ההתנהגות הזו היא שאם חיברנו מודול שיש לו מתודה באותה השם, היא תסתיר לנו את המתודה עם אותו השם במחלקה שאנו יורשים ממנה – ולא כ\"כ משנה סדר ההוספה של המודולים / רישום מתודה מקומית עם אותו השם. זה מצב שאני יכול לדמיין כיצד הוא גורם לתסכול רב למי שלא מבין מה מתרחש מולו…

    ריבוי-צורות (polymorphism) ברובי

    ל OO יש שלושה עקרונות בסיסיים:

    • הכמסה – דיברנו!
    • הורשה – דיברנו!
    • ריבוי צורות…. – נדבר מייד.
    חשוב לציין שריבוי צורות נחשב עיקרון חשוב יותר מהורשה. רעיון ההורשה הוא פשוט \"פוטוגני\" יותר – ולכן מקבל יותר \"זמן מסך\" בספרות ה OO.
    ובכן… כבר ציינו בתחילת הפוסט שבשפת רובי אין מבנים המקבילים ל interface או abstract class בג\'אווה. כיצד אם כן ניתן להשיג ריבוי-צורות בלעדיהם??

    בעצם – האם איי פעם שפה מנעה מאיתנו יישום של רעיונות בכך שלא סיפקה לנו כלים מוכנים ליישם אותם? עיכבה – כן, אבל לא מנעה. חשבו כיצד מתכנתי ג\'אווהסקריפט שמיישמים encapsulation בשפה – למרות שהיא לא קיימת ככלי בשפה (למי שלא מכיר: ע\"י דפוס עיצוב בשם Module / Revealing Module או פשוט ע\"י קונבנציה של קו תחתון בתחילת מתודות שהן \"פרטיות\" – ומשמעת שאין לגשת אליהן מבחוץ).

    הצורך בריבוי-צורות הוא ממשי וגם מתכנתי רובי משיגים אותו, עם מעט הכלים שהשפה מספקת (והיא מספקת כלי או שניים שלא הזכרנו עדיין).

    הנה ה\"דפוס\" המקובל ליישום ריבוי-צורות ברובי:

    class AbstractHerald
    def announce
    raise
    NotImplementedError, \'You must implement the announce() method\'
    end
    end

    class
    OptimisticHerald < AbstractHerald
    def announce
    puts
    \'life is good!\'
    end
    end

    class
    PessimisticHerald < AbstractHerald
    def announce
    puts
    \'... life sucks!! :(\'
    end
    end

    x = OptimisticHerald.new
    puts x.is_a?
    AbstractHerald # true
    puts x.is_a? Hash # false

    האכיפה על כך שנממש את המתודה announce – נמצאת ב runtime (מוזר בג\'אווה, טיפוסי ברובי).
    בעזרת ?is_a – אנו יכולים לבדוק אם מופע שביידנו יורש ממחלקה מסויימת.

    חשוב להזכיר שנטייה טבעית כשמגיעים לשפה חדשה היא לחפש את המבנים המוכרים. חשוב להיזהר ולא להגזים, לא \"לכתוב ג\'אווה ב syntax של רובי\". שפת רובי היא דינאמית מאוד מטבעה – וחשוב להבין ולהתרגל לסגנון זה.

    מה עם ריבוי ממשקים?
    נדמיין לרגע מחלקה בג\'אווה בשם User שניתן לגשת אליה דרך הממשק UserInfo לפעולו ת של קריאת נתונים, ודרך הממשק UserMaintainance – לפעולות עדכון על פרטי המשתמש (היא מממשת את שני הממשקים). המחלקות השונות במערכת מוגבלות לעשות על User רק את מה שהממשק שבידן מאפשר להן – הגבלה שנאכפת ע\"י השפה.

    ברובי אין סוג כזה של אכיפה, ואכיפת הממשקים (שקיימים בדמיון או התפיסה של המתכנתים) – נעשית בצד המחלקה המשתמשת. לפעמים.

    כיצד עושים זאת? המתודה ?respond_to בודקת האם מחלקה (וההיררכיה שלה, המודולים שמחוברים אליה, וכו\') יודעים לטפל במתודה (ע\"פ השם שלה – לא ע\"פ מספר הפרמטרים!).
    אם השתמשתם ב method_missing יהיה עליכם לדרוס את המתודה ?respond_to_missing ולהצהיר על אלו מתודות אתם מגיבים – בכדי ש ?respond_to תמשיך לספק תשובה נכונה (מזכיר את equals ו hash בג\'אווה).

    puts x.respond_to? :announce # true
    puts x.respond_to? :quit_job # false

    הבדיקה מתבצעת ברמת המתודה ולא ברמת המחלקה, מכיוון מתודה למופע ישירות (כפי שראינו קודם) – גם אם שרשרת המחלקות והמודולים שהוא מממש לא מכילות מתודה שכזו.

    לכאורה, כל פעם שאתם שאתם מקבלים כארגומנט אובייקט אחר שאתם לא בטוחים (כיצד ניתן להיות בטוחים!?) שהוא יודע לענות למתודה מסויימת כלשהי – עליכם לבדוק זאת קודם ע\"י ?respond_to. זה \"תיק\" לא קטן.

    בפועל, סביר שתשתמשו ב ?respond_to בנקודות בהן:

    • הקוד שלכם נכשל בהן בעבר, והחלטתם \"לחזק\" (hardening) אותן.
      כלומר – הסיבה לתקלה הייתה מורכבת, ולא טעות הקלדה \"טפשית\".
    • בנקודות שהסיכון לתקלה שכזו הוא בהחלט סביר, בהנחה שאתם מתכנתים זהירים / קפדנים.

    שווה לומר זאת פעם נוספת: ?respond_to לא תבדוק לאלו פרמטרים המתודה מצפה – אלא רק שקיימת מתודה בשם שצויין. כלומר: הבדיקה היא \"בערך\" ולא לגמרי מדוייקת.

    מה עושים כדי למנוע בכל-זאת תקלות? הרבה מאוד unit tests.
    למפתחי ג\'אווה זה עשוי להשמע מחריד: \"שפה שאנו לא סומכים עליה ב 100%?!\", אולם מניסיוני בג\'אווהסקריפט – המצב הזה עשוי לשרת דווקא לטובה.
    הקומפיילר של ג\'אווה מספק ביטחון מסוים (בעצם: תחושת ביטחון מסוימת) – שמהווה מכשול למפתחי ג\'אווה להשקיע הרבה בבדיקות יחידה / אוטומציה. דווקא בשפות דינאמיות שהמפתחים מודעים יותר לכך שהם \"חשופים לתקלות\" – המוטיבציה להשקעה בבדיקות גבוהה יותר, ולעתים גם קל יותר לכתוב ולתחזק בשפות הללו את הבדיקות – מה שיכול להוביל בסה\"כ למערכת אמינה יותר [ב].

    Structs

    לכאורה Struct הוא מבנה פחות משמעותי בשפה. הוא ממומש בעזרת כלים קיימים, וייתכן שהמפרשן של רובי כמעט או בכלל לא מכיר אותו. בכל זאת – הוא שימושי.

    אתם אולי מכירים structs מ ++C או #C – שם \"הקטע\" שלהם היא רציפות בזכרון / יכולת שמירה על ה stack של ה thread. ברובי אין כאלו דברים – והשיקולים הם שיקולים של מהירות פיתוח ותחזוקה. Struct נמצא על הרצף בין Hash (מבנה הנתונים מטיפוס Dictionary – ללא מתודות או קבועים, או הורשה) למחלקות. הוא קצת יותר מ Hash וקצת פחות ממחלקה.

    Struct הוא בעצם generator של מחלקה פשוטה, שכוללת כמה משתני מופע – ו getters / setter למשתנים הללו. היא חוסכת, מצד אחר, הקלדה של מחלקות משעממות, ומצד שני מספקת מבנה מעט יותר יציב / מוגדר-היטב מ Hash. היתרונות העקריים הם:

    • יש הגדרה ברורה לסכימה של ה struct (לאלו פרמטרים מצפים).
    • ניתן לגשת לפרמטרים הללו בצורה יותר אלגנטית: במקום [data[:currency משתמשים ב data.currency.
    כאשר משתמשים ב Hash (שזו דרך מהירה מאוד, ושימושית מאוד בהמון מקרים) לתאר מבנה נתונים – אין מקום ידוע בו מוגדרת הסכמה: איזו שדות קיימים? כיצד בדיוק הם נקראים? הסכמה, פעמים רבות, היא צירוף כל המפתחות בהם עשו שימוש איפשהו בקוד.

    class MyClass
    MyMessage = Struct.new(:source, :target, :text)

    def foo
    some_condition
    = false
    MyMessage.new(\'a\', \'b\', \'c\') unless some_condition
    end

    def goo
    MyMessage.new \'a\', \'b\' # text will be nil
    end

    end

    msg = MyClass.new.foo
    puts msg.text
    , msg.source # c, a

    קצת חבל שה IDE בו אני משתמש, RubyMine, לא מספק auto-complete ממשי ל Structs.

    אם אתם רוצים להוסיף איזו מתודה או שתיים ל struct – אפשר.
    Struct::new יכולה לקבל גם בלוק (של מתודות קבועים) ותמיד אפשר \"לפתוח את ה Struct להרחבה\". כל עוד זה נעשה בצמוד ליצירה שלו – אני מאשר. 🙂

    הנה אופן השימוש בבלוק:

    MyMessage = Struct.new(:source, :target, :text) do
    def encryptText
    self.text = self.text.gsub(/./, \'*\')
    end
    end

    msg = MyMessage.new \'me\' , \'you\', \'a secret\'
    msg.encryptText
    puts msg.text
    # ********

    סיכום

    אם יש פעמים בהן ההערכה שלי לכותבי ספרים טכניים היא הגדולה ביותר, היא ברגעים בהם אני מסויים פוסט שכזה: פוסט ארוך ושוחק. פוסט שכאשר אני מסיים אותו – אני כבר שוכח מה כתבתי בהתחלה ועלי לעשות עוד כמה \"מעברים\" בכדי שהכתוב ישמור על הרמוניה ורצף הגיוני של דברים.
    אני מניח שבכתיבת ספרים העבודה הזו היא ארוכה וקשה פי כמה, אפילו שנניח והפרקים השונים הם כמעט ובלתי-תלויים.

    סקרנו את תכנות מונחה-העצמים ברובי ואני מקווה שהסקירה, למרות שנדחסה כולה לפוסט אחד – היא עדיין מספיק מקיפה ועמוקה.
    שמעו: רובי היא שפה מורכבת. יותר מפייטון (ברור!), יותר מג\'אווהסקריפט (דאאא), ויותר מג\'אווה. אני משווה את המורכבות שלה רק ל ++C (אני לא משווה עדיין ל Scala…). זה לפחות הרושם שלי – בתור אחד שכתב בכולן (לא ממש בסקאלה).

    שיהיה בהצלחה!!

    ——

    קישורים רלוונטיים

    http://rubymonk.com/learning/books מקור טוב ללמידת רובי לעומק

    ——

    [א] אם אתם רוצים לראות את שרשרת ההיררכיה של מחלקה – פשוט הפעילו עליה את המתודה ancestors.

    [ב] מפתחי ג\'אווה, ענו לעצמכם בכנות: איזה אחוז משגיאות הקוד המשמעותיות הקומפיילר \"תופס\"? ואיזה אחוז הוא לא?



    שפת רובי – עוד קצת שטויות שכאלו / או: מדריך מזורז למפתחי ג\'אווה מנוסים

    בואו נבחר כמה דוגמאות שיחשפו בפנינו צדדים נוספים של שפת רובי:


    #1
    p nil # nil

    #2
    puts nil.inspect # nil ; why not NilPointerException !?

    #3
    puts nil.to_s # empty line

    #1

    אנו מקלידים את הביטוי \"p nil\" – ביטוי שנראה קצת מוזר, אולי שגיאה.
    בפועל, p x הוא קיצור ברובי לכתיבת puts x.inspect.
    2 מתודות בסיסיות של המחלקה Object ברובי הן:
    1. to_s (המקבילה של ()toString) – מתודה המחזירה ייצוג של המחלקה כמחרוזת
    2. inspect – עוד מתודה מאוד דומה שמחזירה ייצוג של המחלקה כמחרוזת
    מה ההבדל?
    to_s נועדה יותר ל output למשתמש, בעוד inspect נועדה יותר לצרכים פנימיים – למשל debug.
    ברוב המקרים inspect פשוט תקרא ל to_s, ורק לפעמים היא תציג ערך שונה.
    למשל שימו לב להבדלים:

    puts 1.to_s        # 1
    puts 1.inspect # 1
    puts \'1\'.to_s # 1
    puts \'1\'.inspect # \"1\"

    בעוד to_s מחזירה את אותו הערך, inspect – מספקת לי \"רמז\" על הטיפוס של המשתנה. למשל: העובדה ש \"1\" הוא בעצם מחרוזת  – בעזרת המירכאות שמסביב.

    #2
    טוב… אז p nil שקול בעצם להדפסה של inspect על nil, אבל בעצם – למה לא נזרקת לי Exception?
    התשובה היא ש nil ברובי לא ממומש כ pointer ריק (או מקבילה מודרנית) אלא כ Null Object (אובייקט שמדמה ערך null-י – הקישור מוביל לפוסט בנושא). בעצם nil ברובי הוא אובייקט לכל דבר, כמו כמעט כל דבר אחר (למשל הערכים true ו false המיוצגים ע\"י מחלקות גלובליות בשם TrueClass ו FalseClass, בהתאמה).
    #3
    אז למה ש nil.to_s יחזיר לנו שורה ריקה?
    אם זה לא ברור – חזרו שוב על מה שעשינו למעלה….

    פוסט זה שייך לסדרה: רובי (Ruby) למפתחי ג\'אווה ותיקים

    Special Literals

    הנה כמה צורות כתיבה מקוצרות שעשויות להיות מבלבלות (אם אתם לא מכירים), או שימושיות (כאשר אתם כבר מכירים):

    # large integer literal
    puts 100_000 # 100000
    puts 1_0_0_0 # 1000

    # character literal
    puts ?c # c
    puts \'c\' # same. c

    # hex literals
    puts 0x292 # 658

    # binary literals
    # rwxrwxrwc
    puts 0b100100100 # 292
    puts 0b100100100.to_s(8) # 444

    large integer literal
    בכדי להקל על קריאה של מספרים גדולים – ניתן להכניס קו תחתי \"_\" בין הספרות – וזה עדיין מספר לכל דבר. צורה זו הופכת את המספר \"מאה אלף\" לקל לקריאה, ואפשר להשתמש בה להפוך את אלף להראות כמו… נחש?!

    character literal
    אין ברובי טיפוס כמו char בג\'אווה, אך ניתן להשתמש בקיצור של ? ואחריו סימן בכדי להכריז שמדובר בכוונה במחרוזת של תו אחד. האם \"??\" יעבוד? – נסו ותראו.

    hex literal
    ברור

    binary literal
    תחילית של 0b עם 0 ו 1 אחריה מצהירה שמדובר במספר בינארי. אם לדוגמה אנו רוצים להגדיר הרשאות בלינוקס – פורמט זה נוח יותר לעבודה.
    שימו לב שהמספרים שאנו מכירים כקודים להרשאות (444, 777, וכו\') הם מספרים בבסיס 8, אותו ניתן להציג ברובי ע\"י to_s עם הבסיס הרצוי

    השוואת ערכים

    x = Object.new
    if x
    puts
    \'yeah\'
    end

    בדומה לג\'אווהסקריפט, ניתן להשתמש במשפט if כדרך מקוצרת לדעת אם למשתנה יש ערך.
    רק שימו לב שהערכים הבאים הם evaluated כ true בשפת רובי:

    • מערך ריק
    • המספר 0 (אפס) – בשונה מרוב שפות התכנות
    • מחרוזת ריקה 

    בקיצור: ברובי הכל evaluated כ true, מלבד false ו nil. כאשר רוצים להבחין בין false ו nil – עלינו להשתמש במתודה ?nil

    השמה קלה

    עוד תרגיל תחבירי שדומה לג\'אווהסקירפט היא בדיקה מקוצרת עם ערך הוא nil – והשמת ערך ברירת-מחדל במקום:

    data = {}


    # long
    if data[:currency].nil?
    currency
    = \'USD\'
    else
    currency = data[:currency]
    end
    puts currency # USD


    # short
    currency = data[:currency] || \'USD\'
    puts currency # USD

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

    התחביר המקוצר – נחמד בהרבה.

    ברובי יש תחביר אפילו יותר מקוצר, שמשתמשים בו לא-מעט, בכדי לקצר מקרה של הגנה והשמה לאותו משתנה עם ערך ברירת-מחדל:

    currency = nil

    # short
    currency = currency || \'USD\'
    puts currency # USD

    # even-shorter
    currency ||= \'USD\'
    puts currency # USD

    סבבה!

    עוד תכונה \"מקצרת\" היא שמשפטי if מחזירים בעצמם ערכים (השורה האחרונה בביטוי):

    def born_in_the_USA?
    false
    end

    data[:currency] = if born_in_the_USA?
    @@usa_citizens += 1
    \'USD\'
    else
    \'ruble\'
    end

    puts data[:currency] # \'ruble\'

    הערה קטנה: שימו לב שברובי אין operators של ++ או –. משתמשים ב 1 =+.

    ומה עם הקיצור ל if-else שאנו מכירים מג\'אווה בצורת \"?\" – אי אפשר להשתמש בו ברובי?

    # shorthand
    data[:currency] = born_in_the_USA? ? \'USD\' : \'ruble\'

    puts data[:currency] # \'ruble\'

    אפשר!

    גם משפט case מחזיר תמיד ערך. בהזדמנות זו כדאי להציג את התחביר שלהם, שהוא מעט שונה:

    def name_number(number)
    case number
    when 0
    \'nullus\'
    when 1
    \'uno\'
    when 2..10000
    \'other number\'
    when /\\d+\\$/
    \"that\'s money\"
    else
    \"don\'t know\"
    end
    end

    puts name_number 0 # nullus
    puts name_number 1 # uno
    puts name_number 7 # other number
    puts name_number \'2$\' # that\'s money
    puts name_number \'google\' # don\'t know

    כמה הבדלים:

    • משתמשים במילה \"when\" ולא \"switch\" (כל הכבוד!)
    • ניתן להשתמש בטווחים (כמו בפאסקל – אם אני זוכר נכון)
    • ניתן להשתמש ב regex
    • ניתן לערבב בין כולם באותו ה case

    שימו לב שהביטוי האחרון בפונקציה הוא ערך ההחזרה שלה, ובמקרה שלנו זהו ה case statement.

    ארגומנטים לפונקציה

    הנה נושא פשוט… לכאורה.
    כיצד ניתן להגדיר ברובי פרמטרים של פונקציה, וכיצד ניתן להעביר ארגומנטים?
    מינוח:

    • פרמטרים הם המשתנים בחתימה של הפונקציה שהגדרנו
    • ארגומנט הם המשתנים שהעברנו בהפעלה של הפונקציה

    נניח לדוגמה את קיום פונקציה foo הבאה:

    def foo(a, b, c)
    @@y = a + b * c
    end

    כאשר a, b, c הם פרמטרים, שכיוון שזו שפה דינאמית – לא מגדירים את הטיפוסים שלהם.

    אם נקרא לפונקציה עם ארגומנט יחיד

    foo(3)

    נקבל שגיאה: \"Argument Error: wrong number of arguments\" עם כל הדינאמיות, רובי מצפה שכל הארגומנטים יישלחו (בניגוד לג\'אווהסקריפט, למשל, שתציב undefined בפרמטרים להם לא נשלחו ארגומנטים).

    אפשר לאפשר לשלוח ארגומנט אחד, אם מגדירים ערכי ברירת מחדל לפרמטרים האחרים

    def foo(a, b = 2, c = 5)
    @@y = a + b * c
    end

    foo 3
    puts @@y # 15

    מצב \"מעצבן\"[א] הוא בו אני רוצה לשלוח ערכים רק ל a ו c, אך \"נאלץ\" לשלוח גם ערך ל b כי הוא קודם בסדר ל c. למשל:

    def goo(message, warning = false, log_externally = false)
    message
    += \'!!!!!\' if warning
    puts message
    send_log message
    if log_externally
    end

    goo \'hello\', nil ,true # sending nil = sort of Annoying

    (עברתי מ foo ל goo כדי לייצר דוגמה יותר ריאליסטית)

    דרך אחת לפתור מצב זה הוא שימוש ב options hash:

    def goo(message, options = {})
    message
    += \'!!!!!\' if options[:warning]
    puts message
    send_log message
    if options[:log_externally]
    end

    goo \'hello\', { log_externally: true }

    החיסרון בדרך זו היא הצורך בתיעוד – מה בעצם options אומר.

    הו לא! זה לא מספיק טוב! לא עבור רובי – חובה למצוא פתרון פשוט יותר!

    רובי 2.0 הציגה יכולת שנקראת Keyword Arguments, שבאה פעם אחת ולתמיד לפתור את המצב הלא-נוח שתואר עד כה.

    def goo(message, warning: false, log_externally: false)
    message
    += \'!!!!!\' if warning
    puts message
    send_log message
    if log_externally
    end

    goo \'hello\', log_externally: true

    אין מחויבות על סדר הארגומנטים שנשלחים עם keyword – ממש כמו hash, אבל יש לנו \"תיעוד\" מובנה בחתימה של הפונקציה.

    צילומסך מ Tutorial מפורסם של רובי (Ruby Bits) בו המרצה קופץ על המסך כדי לקרוא סדרה של קריאות התפעלות מיכולת ה keywoard arguments.
    הסימן 1UP בא לסמן קוד שהוא \"!Awesome\", ופה בדיוק הופיעו חמישה כאלו ברציפות.

    מה בכל זאת מפריע לי ב keyword arguments של רובי?

    keywords arguments שיפור נחמד בתחביר, אך יש לו גם צד שמטריד מעט. כדי להסביר את הנקודה הזו, אני צריך לתת הקדמה על הדרך בה המפרשן של רובי פועל. לכאורה זו סטייה מהדרך, אבל מכיוון שמטרתנו היא לטייל ולהכיר את רובי – כל סטייה (מעניינת) היא מבורכת!

    שפת רובי היא דיי מורכבת (יש שיאמרו גמישה) מבחינה תחבירית. יש הרבה \"מבנים\" אפשריים של קוד. כל החלפה של סימן – יכולה ליצור משמעות חדשה.

    כדי לפענח את קוד הרובי (parsing), לא כתבו parser בצורה ידנית. במקום זאת, הגדירו את סט המבנים האפשרי בשפה (להלן parser.y) ואז בעזרת generator של parsers בשם Bison (וריאציה של YaCC[ב]) – מקמפלים parser שמפענח את קוד הרובי – ניתן למצוא אותו כקובץ בשם parse.c.
    לא תמצאו את הקובץ הזה ב Git Repository של רובי, מכיוון שהוא נבנה בזמן ההתקנה עבור המעבד / מערכת ההפעלה הספציפית.

    אצלי (חלונות 7, 64 ביט, רובי 1.9) גודל הקובץ הוא כחצי מגה או 17,000 שורות מורכבות של קוד. לא משהו שמישהו היה כותב בעצמו. שפת PHP, למשל, משתמשת בגישה דומה.

    החל מרובי 1.9, מפרשן ברירת-המחדל הפך להיות YARV (קיצור של Yet another Ruby VM[ג]).
    בעת הפעלת התוכנה YARV \"מקמפל\" את התוכנה ממש לפני ההרצה לשפה בסיסית יותר – הנקראת YARV instructions. העקרון דומה ל JIT Compiler של ג\'אווה, חוץ מזה שבג\'אווה מקמפלים משפת ByteCode לשפת מכונה, וברובי מ עץ Syntax שפוענח – לשפת \"ByteCode\" (ה YARV instructions) שתעבור אינטרפרטציה מעתה תוך כדי ריצה.

    מיותר לציין שהמעבר מ MRI ל YARV מציג שיפור ביצועים משמעותי מאוד לקוד רובי.

    הנה דוגמה לפענוח של קוד רובי ל YARV instructions, שמציגה \"על הדרך\", עוד צורה להגדיר פרמטרים ברובי – args* – המקבילה הישירה (והקצת-יותר-גמישה) של ה varags בג\'אווה. למה יותר גמישה? כי רובי לא תחייב את המתכנת להציב את ה varargs דווקא בסוף רשימת הפרמטרים רחמנא ליצלן! אולי יותר נוח / אלגנטי עבורו לשים אותה דווקא באמצע?

    לכל scope בשפה, רובי מנהלת בזיכרון Local Table (מזכיר במשהו את ה Activation Frames של שפת ++C) שם מנוהלים המשתנים המקומיים / פרמטרים של הפונקציה או הבלוק.

    הטבלה המקומית מגדירה את הערכים, עם הטיפוסים שלהם. בכדי לאפשר args* (טיפוס = Rest) באמצע רשימת הארגומנטים, עלינו להבחין בין ארגומנטים שנשלחו לפני (טיפוס = Arg), לארגומנטים שמגיעים אחרי (טיפוס = Post). המפרשן של רובי יקרא את הארגומנטים שלפני ואחרי, ומה שיישאר – ילך ל args*.

    שימו לב ל YARV instructions משמאל – זהו בעצם הקוד המקומפל (סוג של Bytecode) אותו המפרשן מפענח ומריץ בזמן הרצת התוכנית.

    בואו עכשיו נראה איך נראית פונקציה פשוטה עם ערך ברירת-מחדל לפרמטר:

    ועכשיו איך נראית אותה הפונקציה, עם Keyword Argument:

    מ-ס-ו-ב-ך.

    נכון: מה שהסתבך הוא ה byte code. לא משהו שמפריע לפריון של המפתח. אבל… בתוכנה אין ארוחות חינם[ד].
    byte code מסובך יותר משפיע על:

    • ביצועים (קצת)
    • יציבות / אמינות (קצת)
    • קלות בביצוע debug (קצת)
    • קושי לבצע שינויים משמעותיים בפלטפורמה – לדוגמה שיפור התמיכה ב parallelism וב concurrency.
    שפת Go, למשל, (שאני מאוד מחבב) – החליטה לוותר על Generics בשפה בכדי לשמור על קומפילציה מהירה.
    ברור ששפת Go (שהיא שפה ל System Programming) היא בערך ההיפך הגמור משפת רובי (שפת high level שמתמחה במהירות פיתוח גבוהה) – ולכן הגיוני שההחלטות שלה בתחום יהיו הפוכות לגמרי לאלו של רובי.
    קרוב לוודאי שאלו גם קשיי הסתגלות אישיים שלי, לשפה חדשה ולתפיסות העולם השונות שלה.
    רוב חיי כתבתי בשפות \"נמוכות\" יותר (C, פאסקל, עד #C ג\'אווה) או כאלו שקידשו פחות את הקריאות ונוחות המשתמש (ג\'אווהסקריפט).
    התרגלתי לכך שהקוד שלי לעולם לא יהיה \"מ-ו-ש-ל-ם\", ולמרות חוסר הכיף שבכך, לפעמים עושים בקוד כמה \"צעדים טכניים\" שהם חלק ממגבלות השפה. למשל: לשלוח nil כמה פעמים כארגומנט – כי אין משמעות לפרמטר בשימוש הנוכחי. התרגלתי וזה נראה לי בסדר.

    הגישה של רובי היא \"לעשות מפתחים מאושרים\", ובמובן הזה הגיוני שהיא תתקדם בכדי לשפר את השפה עוד מעט כל פעם. השאיפה הזו יכולה ליצור מצבים מעט מוזרים.

    למשל, בואו נתבונן על כלי קצת פחות מוכר בשפת רובי: ה Flip Flop Operator (בקיצור FFO):

    (1..20).each do |x|
    puts x if (x == 5) .. (x == 10)
    end

    FFO הוא הנקודתיים בין 2 תנאי ה if. משמעותו: קיים את התנאי כל עוד התנאי הראשון מתקיים עד הרגע בו התנאי השני מפסיק להתקיים. את הקוד הנ\"ל אפשר לכתוב בעזרת \"קטן מ…\" ו\"גדול מ…\". האם שיפור הקריאות מצדיק הוספה של אופרטור נוסף לשפה?

    הנה דוגמה שקצת יותר מצדיקה שימוש באופרטור שכזה… איתור מקטעים (flip..flop) ברצף:

    [3,5,5,19,100,10,1,0,10,2].each do |x|
    puts x if (x == 5) .. (x == 10)
    end

    # result => 5, 5, 19, 100, 10

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

    הייתה כבר בקשה רשמית להסיר את האופרטור מהשפה, אך הבקשה לא נכללה בתכולה של גרסה 2.0. מאטצ\' הוסיף שלא יציג שינויים לא תואמים לשפה ברובי 2 – אולי זה יקרה ברובי 3…

    סיכום

    אני מקווה שהיה דיון מעניין, והצלחתי להעביר בצורה יעילה כמה מהתכונות של שפת רובי במהלך הפוסט.
    נשאר עוד הרבה לדבר עליו, עוד לא נגענו בתכונות ה OO בשפה.
    ארצה להמשיך בסדרה בצורה שתהיה יותר ממוקדת בהבנת השפה עבור מפתח ג\'אווה שעובר לשפה. למשל: אני 🙂

    שיהיה בהצלחה!

    —-

    קישורים רלוונטיים

    Rubular – \"מחשבון\" regex אונליין לרובי

    [א] מתכנתים ברוב שפות התכנות האחרות פשוט מתרגלים לחוסר הנוחות המקומי, בעוד בשפת רובי עושים מעל ומעבר כדי למנוע מצבים של קוד לא-אלגנטי, אפילו \"בקטנה\". זה עניין של תרבות.

    [ב] YaCC הוא קיצור של Yet another Compiler Compiler – ונחשב הכלי הנפוץ בתחום (ביחד, אולי עם ANTLR).

    [ג] הידוע גם בשם (KRI (Koichi\'s Ruby Interpreter על שם המחבר שלו, בהתאמה ל MRI שנכתב ע\"י מאטצ\'.

    [ד] יש מיתוס, בקרב אלו שלא חוו מספיק tradeoffs בתוכנה על בשרם, ש \"אם נשים את האנשים המוכשרים ביותר – הם ימצאו דרך \'למחוק\' את ה tradeoff ולהעלים אותו\". בקשה זו היא כמו לקוחה מהמיתולוגיה היוונית – והתוצאה שלה גם היא לעתים יוונית: טרגדיה.
    סיפור מפורסם הוא של ספינת המלחמה Vasa בו זימן אליו מלך שוודיה את אדריכל הספינות המפורסם בעולם (באותה התקופה) כדי שיבנה את ספינת המלחמה הטובה בעולם: ספינה שהיא גם ספינת משא (לחיילים) הדורשת מקום רב ויציבות, וגם ספינת קרב – הדורשת זריזות ויכולת תמרון. גם וגם. זה היה פרויקט אדיר, ומרשים – שלא נראה כמותו באותם הימים. לאחר עבודה שכללה מאמצים אנושיים אדירים ומפוארים – הספינה הושקה למים. היא הצליחה לשלב גמישות קרבית ונשיאת חיילים – למרחק של 1300 מטר בלבד, שם משהו השתבש והיא שקעה למצולות. הטיטאניק בגרסה המקוצרת.

    ייתכן והמשך עבודה על פרויקט הספינה היה יכול להפיק, לאחר מספר מסוים של איטרציות, שילוב בין 2 הדרישות. כנראה שעדיין היא לא הייתה מצטיינת באף אחת מהדרישות – ובטוח שהיא הייתה מסובכת יותר מספינה המיועדת למשימה אחת בלבד.

    חשוב לציין שכן ניתן \"לרמות\" tradeoffs במידה מסוימת, וזה ע\"י החלפה של הבחירה ב tradeoff ברגעים שונים בתוך אותו התהליך. למשל כמו האופן בו Cassandra \"מרמה\" את ה CAP Theorem (שטוען שלא ניתן להשיג גם זמינות וגם עקביות במידע מחולק (partitioned)) – אבל Cassandra מאפשרת לכל query בודד לבחור בין יותר זמינות או יותר עקביות בנתונים, וכך \"כמערכת\" – Cassandra מספקת גם וגם.

    Amazon Web Services – הצצה ראשונה

    שירותי AWS, כך מספרים, נולדו מתוך מחסני הספרים של אמזון. בתחילת שנות ה 2000 אמזון הייתה כבר מקצוענית בתפעול מחסנים. בכדי לייעל את עלויות התפעול בקצת יותר החליטה אמזון להשכיר לגורמים חיצוניים שטחי אחסון במחסנים שלה, שלא היו באותו הרגע בשימוש – וכך להגיע לנצילות גבוהה יותר של הנכסים שלה.

    \"מדוע להשכיר אחסון פיסי, אך לא אחסון לוגי?\" – מישהו באמזון שאל.

    אמזון הצטיינה לא רק בלוגיסטיקה פיסית אלא גם בתוכנה. בכדי לנהל חנות מקוונת כ\"כ גדולה, ובזמינות גבוהה – נדרשו תשתיות טכנולוגיות שלא היו רבות כמותן בעולם. בכלל, חברות הטכנולוגיה שפרחו (אמזון, נטפליקס, פייסבוק וכו\') הן החברות שהיו גם חזקות טכנולוגית מספיק, בכדי לאפשר צמיחה מהירה מבלי להיעצר. על המתחרות שהיו מאוד מרשימות אך היו חסרות את הטכנולוגיה לצמוח בקצב מהיר (גמזון, נטפליפפלופ ופייסבורד) – לא נשמע לעולם.

    אמזון החלה לשווק את AWS בשנת 2006, בעיקר על בסיס של EC2 (שירות ניהול VMs) ושירות S3 (אחסון מסמכים).
    השירותים הללו לא היו חדשים – אמזון השתמשה בהם כבר כמה שנים, באינטנסיביות לצרכיה הפנימיים. כשנחשפו לשוק הם כבר היו מוצרים בוגרים (בקטגוריה) ומוכחים.

    שירותים נוספים היו Simple Queue Services (בקיצור: SQS) ו SimpleDB (בסיס נתונים רלציוני כשירות, עבור כמויות קטנות של נתונים. כיום, נראה שאמזון נמצאת בשלבים להפוך אותו ל deprecated לטובת שירותים חדשים יותר).

    על הבסיס הזה היה ניתן להקים מערכת מבוזרת, High Scale ו Highly Available – ובזול. היה עדיין צריך לעבוד הרבה יותר קשה מהיום בכדי לבנות פתרון על AWS, וכל הרעיון של מחשוב ענן היה עוד חדש – כך שלאחר שנה היו \"רק\" 180 אלף מפתחים רשומים לשירותי AWS.

    היום, מיותר לציין, אמזון היא \"הגורילה\" של שירותי ב IaaS ובדרך המבטיחה להיות גם \"הגורילה\" של שירותי ה PaaS עם AWS Beanstalk. מיקרוסופט, גוגל, יבמ, ו Heroku הצליחו לספק בשנים האחרונות תחרות מוגבלת בלבד.

    מדוע אם כן – אמזון לא מעלה מחירים? מדוע היא כ\"כ אגרסיבית בתמחור של השירותים שלה?

    1. זה עניין תרבותי, כך טוענים. אמזון מתמחרת שירותים בצורה אגרסיבית מיום היווסדה.
    2. קצת יותר מפתיע: עם כל ההצלחה של הענן, כח המחשוב שנמצא היום בענן הוא רק כ 5% עד 10% מכח המחשוב העולמי (המספר המדויק תלוי במי מחשב וכיצד). קרב השליטה בענן עוד נמצא בשלביו המוקדמים.
      חברות הענן לא מתמקדות כרגע ברווחים (הן מוכנות להיות break even לבינתיים) אלא רק בצמיחה ותפיסת נתח גדול יותר מפלח השוק.
    מייקרוסופט, לדוגמה, עשתה חייל בשנה האחרונה: היא העבירה את הפוקוס שלה ממובייל – לענן, והצליחה תוך כך להעביר Enterprises רבים שהם \"Microsoft Shop\" – ל Azure.

    התרשים למעלה הוא מעניין, אבל עלול גם להטעות – ולכן יש לי רגשות אמביבלנטיים לגבי ההוספה שלו.
    אם נראה מהתרשים שמייקרוסופט הולכת להשוות לאמזון תוך כמה שנים – אז חשוב לאזן: מייקרוסופט עושה את הרווחים שלה מ Full Stack של טכנולוגיות, בעוד Amazon רק מהשכבות הנמוכות (בעיקר IaaS). כשאמזון נכנסת עתה לשכבות גבוהות יותר (PaaS, DBaaS, וכו…) – היא צפויה \"לפלוש\" לפלחי שוק גדולים וחדשים עבורה – ולהשיג \"קפיצה\" בהכנסות.

    בידיעה ש 90% מהשוק עוד נותר לכיבוש – ברור שאמזון לא נשארת שאננה, וממשיכה בתחרות עיקשת ובכל המרץ.

    כיום, 8 שנים אחרי ששוחררה לראשונה לקהל הרחב, יש ל AWS עשרות שירותים בענן:

    כל שנה, אמזון חושפת כמה שירותים חדשים. עוד שנתיים-שלוש, הרשימה למעלה תראה חלקית ולא מעודכנת בעליל.

    יצירת instance של EC2 – איך זה נראה?

    אני מאמין שאין טוב ממראה עיניים – ולכן אציג את התהליך שעשיתי לפני כמה ימים. שימו לב שיש כמה פרטים פחות מעניינים – ועליהם אני אדלג.

    הערה: עם Deployment כל 10 שניות של עדכון למערכות אמזון – הכל יכול להשתנות בכל רגע. אל תניחו שעוד חצי שנה התהליך יראה בדיוק אותו הדבר – אבל הוא כנראה יהיה דומה.

    אמזון מאפשרת מסלול בשם AWS Free Tier בו אתם יכולים להתנסות בשירותים AWS, על חומרה מינימלית (לא הולכים עם חומרה כזו ל production) ותוכנה חופשית – למשך כשנה (!). מכניסים אמנם כרטיס אשראי – שיחויב רק אם \"התפתתם\" לבקש קצת מעבר למינימום שמוצע בתכנית.

    העלויות המורגשות באמזון, בשלבים אלו של פיתוח ראשוני, הם לרוב לא החומרה שבשימוש – אלא החומרה שלקחתם לשימוש ושכחתם להחזיר. איזה שרת בינוני שלקחתם לכמה שעות, אבל נשאר רץ כל סופהשבוע – למשל.

    במהלך ה Wizard למטה תראו אופציות מסוימות המסומנות כ Free tier eligible. המשמעות היא: \"ניתן לבחור באופציה זו ללא חיוב, כחלק מתוכנית ה Free Tier\". אני אבחר מבין אופציות אלו בלבד.

    בחירת ה AMI

    Amazon Machine Image (בקיצור AMI) הוא הפורמט של אמזון ל VM Image (בעברית: תמונת מכונה-מדומה). אני לא בקיא בפרטי המימוש הפנימי של AMI, אך הפונקציונלית היא כמו זו של קובץ dmg. ב Mac או קובץ ovf. עבור Virtual Box או VMWare.

    את הקובץ ה AMI, אתם לא מנהלים אצלכם – הוא מנוהל על שירות S3 (קיצור של Simple storage service) של אמזון – שירות אמין במיוחד, scalable, לאחסון קבצים בגודל של עד 5TB (נכון לרגע זה). אתם יכולים להשתמש ב AMI מוכן מבית אמזון (הרשימה למעלה), לקנות AMI מצד-שלישי (יש Marketplace שלם, הכולל עלות שימוש לשעה המכילה את החמורה מאמזון + רשיונות התוכנה המתאימים), או פשוט לקחת snapshot מ EC2 instance שבבעלותכם.
    לדוגמה: ליצור instance חדש של Red Hat Linux, להתקין עליו VIM – וכך ליצור AMI חדש \"Red Hat /w VIM\".

    שווה לציין שאם אתם זקוקים ל Landscape גדול, זו עבודה קצת סיזיפית לייצר instance אחר instance. לשם כך יש שירות של אמזון שנקרא Cloud Formation המאפשר יכולות דומות ל AMI – רק בקנה מידה גדול. במקום הקמה של מכונה בודדת – הקמה של Landscape של מכונות (כל אחת עם ה AMI שהוגדר לה), הגדרות רשת, אבטחה וכו\' וכו\'.

    בחירת חומרה (וירטואלית)

    השלב המשמעותי הבא הוא לבחור חומרה (וירטואלית, כמובן). חומרת השרתים מסווגת לפי אופן השימוש:

    • כללי / מאוזן – סדרה t או m3
    • CPU intensive – סדרה c3
    • Memory Optimized – סדרה r3
    • Disk optimized – סדרה i2 או hs1
    • וכו\'.
    בתוך כל סדרה יש כמה \"גדלים\" שונים של מכונות (\"T-Shirt Size\")
    • small
    • medium
    • large
    • xlarge
    • וכו\'.
    אי אפשר לדעת איזו חומרה פיסית תריץ את ה VM שלכם. אם אתם בררניים (למשל: כתבתם קוד ב C בהסתמך על מעבדים מסדרה מסוימת של אינטל) – ההמלצה של אמזון (כפי שאני מכיר אותה, קצת ישנה) היא לבקש instance, לבדוק מה יצא – ואם לא אהבתם \"לזרוק\" ולבקש חדש עד שתהיו מרוצים.

    בחירת Storage





    השרת שלכם זקוק ל Storage. שתי האפשרויות הבסיסיות הן כדלהלן:

    • Ephemeral Storage (אחסון זמני, נקרא גם Instance Storage) – מה שמוקצה כחלק מה VM.
      כשתחזירו את המכונה לרשותה של אמזון – כל המידע יימחק.
      אם ה VM של אמזון קרס (יכול לקרות) – המידע יאבד.
      אולי תוכלו לייצר AMI מהמכונה וכך לשמור עותק של המידע.
    • EBS (קיצור של Elastic Block Store) הוא בעצם סוג של NAS (קיצור של Network Attached Storage) ממנו ניתן להקצות חלקים ולעשות mount ל instance שלכם.
      EBS ממשיך לחיות אחרי שהמכונה הוחזרה / ה VM נפל.
      EBS הוא אמין יותר, ועובר רפליקציה בין Availability zones בתוך ה Region – הכוונה, בין Data Centers (הנקראים Availability Zones) החברים באותו אשכול של Data Centers – שנקרא Region. על כך בהמשך הפוסט.
      הוא עדיין לא אמין כמו S3, ומומלץ לבצע גיבויים למידע – אם הוא חשוב לכם.

    יש לאמזון שירותי Storage נוספים, שזמינים כשירות (כלומר: API) ולא כ mount למערכת ההפעלה:

    Simple Storage Service (בקיצור S3)

    • Key/Object Storage. למה Object ולא Value? – כי לרוב מאחסנים בו \"קבצים\" ולא ערכים קטנים (למשל מחרוזת או מספר).
    • Static files – ניתן לגשת ישירות לקובץ ב HTTP ו/או HTTPS (ואז הוא מוגן בעזרת הרשאות)
    • אמין בצורה יוצאת דופן (durability של 99.999999999% או משהו דומה). יש רק מקרים בודדים מתועדים בהם מידע אבד מ S3, ולרוב זה היה בצורת corruption של נתונים בעקבות באגים של אמזון.
    • שומר אובייקטים בגודל של עד 5TB
    • המידע נשמר מוצפן על השרתים של אמזון (encrypted at rest)
    • זמינות השירות היא גבוהה (99.99%). שימו לב: יכול להיות שהקובץ \"חי וקיים\", אך אין שרת זמין שיגיש אותו (ולכן הפער בזמינויות).
    • יש לו אינטגרציה ל Glacier – אחסון \"קר\" יותר (קפוא!), וזול יותר.
    • יש לו אינטגרציה ל CloudFront – שירות ה CDN של אמזון, על מנת להגיש את הקבצים ממיקומים קרובים יותר לצרכן.
    • Region-Specific, מסתנכרן בין ה Availability Zones שונים.
    • קל מאוד לשימוש (REST API פשוט).

    Glacier

    • Archival Storage – מיועד לשמירת כמויות מידע, ב offline.
    • לקוח 2-6 שעות לשלוף קובץ (מה שמרמז שהקובץ מאוחסן על קלטות?)
    • זול מאוד (סנט ל GB לחודש, כשליש מ S3). עיקר המחיר הוא בשליפה של הנתונים.
    • הנה השירות הראשון של אמזון שאנו נתקלים בו – שהוא בעל תמחור מורכב:
      אמזון מציעים לכם שירות זול – הכי זול שיש, כנראה.
      אבל… התמחור הוא זול כל עוד אתם מצליחים לעמוד בכמה תנאים. זה לא ניסיון של אמזון להכשיל אתכם, זו הדרך שלהם להציב תנאים בהם הם יכולים להציע מחירים כ\"כ זולים. ואלו תנאים שידרשו מכם מאמץ.
    • למשל: בכדי לצמצם את מחיר השליפה מ Glacier עליכם לקבל (download) את הקובץ בקצב הורדה קבוע לאורך 24 שעות, ויש הנחה אם אתם מורידים קובץ שלם ולא חלק ממנו. פרטים נוספים.
    • אתם אמורים להבין ששירות כזה לא מתאים לסטארט-אפ תפרן בתחילת דרכו (כי הוא עלול להתקשות בתנאי התמחור הזול), אלא לארגון מתופעל היטב שמנסה לצמצם עלויות לרצפה – ומוכן להזיע קצת בשביל זה.

    Security Groups

    כל Instance של EC2 משויך ל Security Group. השם עשוי מעט לבלבל – אולי עדיף היה לקרוא להם Traffic Policy.
    כל קבוצה מכילה מספר EC2 instances וקובעת עבורם איזו תקשורת נכנסת תתאפשר ע\"פ פרוטוקולים, מקור, או ports שהגדרתם.  ההגבלות הן על תקשורת נכנסת בלבד – לא מגבילים תקשורת יוצאת.
    מכירים יכולת שכזו בעולם ה On-Premises? נכון – זה נקרא Firewall.
    בעצם ה Security Groups היא דרך קלה ומהירה להחיל אבטחה בסיסית של Firewall על השרתים שלכם. הריכוז בקבוצות עוזר לנהל מצב בו יש לכם instances רבים.
    באופן מעט מוזר, לא ניתן להזיז instance מרגע שהוגדר – בין Security Groups. אתם יכולים לשנות את ההגדרות של ה Security Group או \"לזרוק\" את ה instance ולהקים אחד חדש ב Security Group אחר.
    החלפת מפתחות

    טוב. ה instance שנמצא באמזון מוכן להתניע – אבל הם מוסרים לכם אותו?
    הרי הוא עכשיו באמזון, ואתם – ובאמצע יש רשת עוינת של האקרים. אז… לשלוח או לא לשלוח את פרטי החיבור (\"Admin\"/\"Admin\") במייל וזהו?

    הפתרון (הטבעי / הגיוני) של אמזון הוא להשתמש בהצפנה א-סימטרית. אתם יכולים לייצר זוג מפתחות ולשלוח אחד לאמזון, או לבקש שאמזון תיצור זוג מפתחות – ותשלח אחד אליכם (האופציה הזו יותר קלה).
    במקרה זה תורידו (ב https, כמובן) את המפתח שלכם כקובץ perm. ותתבקשו מאוחר יותר להציג אותו בחזרה – כאשר תרצו להתחבר למכונה.
    במכונות עם מערכת ההפעלה \"חלונות\" תעלו את הקובץ ותקבלו ססמה שעליכם לזכור, במכונות לינוקס תדרשו לצרף את הקובץ עצמו ככלי האימות ל ssh בעת חיבור למכונה. אם לינוקס – אז כדאי שתשמרו היטב את הקובץ שלא יאבד (מקסימום – \"זרקו\" את המכונה והקימו חדשה. זהו הקצב באמזון: שרת = disposable).

    קצת על הפריסה של אמזון

    שירותי AWS פזורים על 27 Data Centers ברחבי העולם (AWS Global Infrastructure – לצורך עדכונים במצב)

    Availability Zone (בקיצור AZ) הוא Data Center עצמאי, עם רשת משלו, הספקת חשמל משלו, אבטחה משלו, עמידות בפני שריפות, אסונות טבע (טוב – רובם) משלו וכו\'. הוא אמור להיות חסין לקריסה של Availability Zone אחר.

    ה AZs מקובצים לתוך אשכולות בשם Region – שזה אזור גאוגרפי בעולם. יש בין ה AZs ב Region רשת מהירה, שמאפשרת רפליקציה של נתונים בין ה AZ בכדי ליצור ולשמר יתירות. Region יכול להיות \"מזרח ארה\"ב\" או \"גרמניה\".

    סה\"כ לאמזון יש כיום 11 Regions ברחבי העולם.

    רבים מהשירותים של אמזון הם זמינים ברמת ה Region. למשל שירות S3 – מסנכרן לבד את הנתונים שלו בין ה AZs ב Region.
    שרתים שלכם (EC2 instances) – הם באחריותכם: כדאי שתפזרו את השרתים ב Region על פני כמה Availability zones שונים, ואם יש צורך בסנכרון – הוא עליכם.

    בהרבה Regions יש שלושה Availability Zones. למה שניים לא מספיקים?

    ראשית – כי 3 זה יותר בטוח מ 2.

    נניח שהסבירות ששני AZs יקרסו באותו הזמן היא קטנה מדי בכדי להטריד אתכם (נניח), האם יש טעם לפזר את השרתים על יותר משני AZs? התשובה הפשוטה היא – כן.

    נשווה פיזור של אפליקציה בין 3 ל 2 Availability Zones:

    • ברגע הנפילה – ה capacity שלכם ירד ל 67% ולא ל 50%.
    • \"להרים instance זה עניין של כמה דקות ב AWS, נכון?\"
    • נכון – אבל לא בעת אירוע של נפילת Availability Zone. במקרה כזה אלפי לקוחות של אמזון מבקשים ממנה instances חדשים באותו הרגע ממש – מה שאומר שהזמן לקבל instance חדש יהיה ארוך מהרגיל, זמן בו יהיה עליכם להסתפק בקיבולת שנותרה לכם – וכאן ההבדל בין 50% קיבולת ל 67% קיבולת – היא יותר משמעותית.

    נטפליקס, למשל, שהעסקים שלהם מבוססים לגמרי על AWS עשו מעבר לכך: ברגע שנופל AZ הם מורידים את איכות השירות (נאמר: ל 65%) בכדי להמשיך לתת את השירות לכל הלקוחות ללא הפרעה – ומבלי להסתמך על כך שיוכלו להוסיף בזמן קצר עוד EC2 instances מספיקים. שיפורים אלו נעשו מתוך outages שהם חוו בפועל, ולא מתוך \"תכנון מוקדם\".

    למה אמזון לא מחברת את כל ה Regions שלה אחד לשני?

    1. כי תעבורת הרשת תהיה יקרה מאוד (יש הבדל בין תקשורת מהירה בתוך וירג\'יניה לתקשורת מהירה בין וירג\'יניה לסין)
    2. כי קשר = תלות = פחות disaster tolerance.
    3. כי שירותים מסוימים (לדוגמה VPC) לא יעבדו על פני מרחק גיאוגרפי גדול.

    אחד ה Regions המעניינים הוא GovCloud שנמצא בצפון-מערב ארה\"ב. אחד החסמים של גופים ממשלתיים להשתמש בשירותי הענן של אמזון הוא רגולציה. כל מיני חוקים שמבטיחים שרק אזרחים אמריקאים יהיו מסוגלים לגשת לשרתים ולרשת, חוקים של אבטחת מידע ואחסון ייחודיים (מישהו שמע על ITAR?) וכו\'. מכיוון שהמגזר הציבורי בארה\"ב הוא מספיק גדול להצדיק השקעה שכזו (הממ הממ) – אמזון בנו Region מיוחד רק לגופי ממשלה בארה\"ב שתפור לכללי הרגולציה. המיקום במערב ארה\"ב נקבע בגלל סיבות טקטיות – והוא יורחב גם למזרח ארה\"ב בעתיד (אני מניח שכ Region נוסף).

    שימו לב שלא כל השירותים של אמזון זמינים בכל ה Regions.

    Edge location
    אמזון, כספקית CDN שצריכה להיות \"קרובה\" לכל המשתמשים בעולם, מחזיקה גם אתרים (מה שנקרא לעתים PoP – Point of Presence) רק לצורך Caching של נתונים או שירות ה DNS (נקרא Route 53).

    קצת על אבטחה

    מודל האבטחה של AWS הוא מודל של אחריות משותפת: של אמזון \"בשותף\" עם הלקוח.
    אמזון לוקחת אחריות על כמה תחומים שלה, ומספקת ללקוח מגוון כלים – בציפיה שהוא יהיה אחראי על הצד שלו.

    אחריות הלקוח כוללת:

    ניהול משתמשים והראשות. אמזון מספקת את IAM (קיצור של: Identity and Access Management) שהוא סוג של Repository לניהול משתמשים (כמו Active Directory), הכולל גם יכולות של Federated Identity ו SSO (כמו Active Directory Federation Services – ADFS).

    אמזון מספקת גם יכולת Multi-Factor Authentication. למשל: הכנסת ססמה ב Desktop ובנוסף אימות ה Login מתוך הסמארטפון (על בסיסי ID ייחודי של היצרן – שמוודא שזה אכן הטלפון שלכם) או חומרה ייעודית אחרת (כמו כרטיס RSA  המייצר קוד זמני שהשרת יכול לאמת – למי שמכיר). ייתכן ותרצו לאכוף מנגנון שכזה על גישה של משתמשי מפתחי (Admins למיניהם).

    אמזון מספקת גם שירותי ייעוץ בנושא אבטחה, אותו תוכלו לשכור מאמזון (בתשלום, כמובן).

    Security Groups – אותם הזכרנו בהקדמה, שזה סוג של Firewall.

    הרשאות שונות (ACLs) – מי יכול לגשת לאיזה שירות (שירותים כמו בסיסי נתונים כשירות, CDN, וכו\').

    אם אתם רוצים לאבטח את הרשת שלכם יותר מהרמה של Security Groups ו ACLs, אמזון מספקת יכולת בשם Virtual Private Cloud (בקיצור VPC) בה תקבלו רשת נפרדת משאר הענן (ע\"י ויראוליזציה, כמובן) ותהיה לכם יכולת שליטה רבה על הגדרת הרשת וההרשאות שלה – כרצונכם. זו כמובן התעסקות לא קטנה, הדורשת הבנה טובה של הרשת – ושל יכולות ה VPC של אמזון.

    אחריות אמזון כוללת:

    הגנה פיסית – אף אדם בעל שלא מורשה לכך לא יוכל לגשת פיסית ולהתחבר לחומרה שמשרתת אתכם. איך עושים זאת? קירות בטון, דלתות פלדה, מצלמות אבטחה, שומרים וכלבים כעוסים. וכמובן: תרגול האופרציה הזו שתהיה בכושר לטפל בתקריות האפשריות.

    אחריות על הגנת הרשת הפנימית של AWS: ניהול ה switches / routers בצורה מאובטחת. אמזון מנטרת את תנועת הרשת מפני חריגות עקרוניות: שרת ששלוח IP Packets ומצהיר על IP שאינו שלו, פעילות של Port scanning וכו\'. אמזון יודיעו לכם אם הם מזהים התנהגות חשודה כלפי ה EC2 Instances שלכם.
    המדיניות של אמזון אוסרת עליכם, למשל, להשתמש ב NMAP או כלי דומה לבצע port scanning לעצמכם. עליכם לסמוך על אמזון. אם אתם רוצים לבצע להריץ Web Scanner על השרתים שלכם – עליכם לתאם זאת עם אמזון מראש ולעשות זאת רק במסגרת הזמן שהוקצב לכם.

    אמזון מבטיחה Multi-tenancy של המערכות שלה (ע\"י Hypervisors, VLANs, או Multi-tenancy ברמת התוכנה, וכו\') שמבטיח שלקוח אחר, שמשתף אתכם חומרה פיסית – לא יוכל לנצל עובדה זו בכדי לגשת לנתונים שלכם. הפרדה מוחלטת.

    שירותי הגנה מ DDoS (כלומר: Distributed Denial of Service) – התקפה נפוצה, שירות כזה מסופק ע\"י רוב ספקיות ה CDN הגדולות, ואמזון היא אחת מהן.

    תאימות לסטנדרטי-אבטחה מרכזיים בתעשייה. לדוגמה: אם הרגולציה של HIPPA (אחסון מידע רפואי) דורשת השמדה פיסית של אמצעי האחסון של מידע רפואי שמפסיקים לשמור – אמזון תספק את השירות הזה עבורכם, ותשמיד במיומנות רבה את הדיסקים הפיסיים ששימשו אתכם לאחסון המידע הרפואי. הכל, ע\"פ הנחיותיכם ותמורת תשלום מתאים – כמובן.

    סיכום

    אחרי שסקרתי את OpenStack (גם חלק שני) – יהיה רק הגיוני לסקור את AWS.
    שילבתי בפוסט לא מעט פרטים – אך הדרך להכיר את AWS היא עוד ארוכה.

    שיהיה בהצלחה!

    כיצד מגדירים ארכיטקטורה? צעד אחר צעד

    אני מאמין, שדרך מצוינת ללמד נושא היא פשוט בעזרת הדגמה. בפוסט הזה אני רוצה לבצע אתכם ביחד תרגיל פשוט בארכיטקטורת תוכנה – בכדי להראות כיצד התהליך הזה נראה.

    שימו לב: בד\"כ כאשר יש ב\"תרגילים לימודיים\" יש סוג של אחיזת עיניים קלה. המרצה \"מאלתר על הלוח\" פתרון מאוד פשוט ומאוד אלגנטי – אבל כנראה שזה לא היה הפתרון אליו הוא היה מגיע בפעם הראשונה. בעצם: הוא פתר את הבעיה כבר קודם לכן, ועשה על הפתרון כמה איטרציות של אופטימיזציה.

    התוצאה: מסר יותר חד (\"כך כותבים קוד נקי!\"), בד בבד עם אשליה שאנשים באמת כותבים קוד שכזה \"במכה ראשונה\".

    את הפוסט הזה אני כותב בספונטניות, לא בגלל אידאל כלשהו – אלא בעיקר בגלל חוסר-הנכונות להשקיע בהבאת הדוגמה \"לשלמות\". תוך כדי כתיבה אנסה לשתף במחשבות שעוברות לי בראש עד לפרט הקטן – מה שעלול ליצור מעט התפלספות, אך אני מקווה שיכול להביא ערך למתכננים צעירים שיש להם פחות ניסיון משלי.

    ארכיטקטורה, להזכיר, היא סדר וצורה של התוכנה שלנו, וגם קבוצת ההחלטות שקשה לעשות בהן שינוי (ולכן אולי כדאי להקדיש אליהן מחשבה בשלב מוקדם).
    תהליך ה\"ארכיטקטורה\" נראה לפעמים כמעט כמיסטיקה – ואני מקווה שפוסט זה יתיר חלק מהערפל (ואולי גם מההילה).
    לצורך התרגיל בחרתי בבעיה קלאסית במדעי המחשב (שאני פחות או יותר פותר בעצמי כרגע כחלק מלמידת שפת רובי): \"בעיית שבע המלכות והפרש\". כיצד ניתן להציב על לוח שחמט 7 מלכות [א] ופרש מבלי שיאיימו זה-על-זה.
    אני לא אתמקד בתהליך חישובי אופטימלי (מדעי המחשב), כי אם בארגון מוצלח של הקוד למחלקות ופונקציות (\"ארכיטקטורה\" / הנדסת תוכנה).

    בואו נצא לדרך.

    כיצד מתחילים להגדיר ארכיטקטורה?

    הרי אפשר להיתקע ולבהות ב IDE (\"אני בוהה בו, הוא בוהה בי\") זמן-מה מבלי להגיע להחלטה כיצד להתחיל.
    כאשר יש כיוון – הקוד רץ מעצמו, \"7 המלכות והפרש\" זה לא באמת \"אתגר\" תכנותי עבורנו. הקושי לרוב יהיה לבחור כיוון ראשוני לארכיטקטורה.
    דרך אחת היא פשוט מאוד להתחיל ולכתוב בלי סדר – ואז לעצור, לספוג תובנות מהקוד, ולהיעזר בהן בכדי לקבוע את הכיוון ולבצע Refactoring. לא לכולם זה הולך בקלות.
    שיטה קטנה אחרת היא כלל שמגיע מתכנון-מונחה עצמים, ו DDD: \"המלים בהן אנו משתמשים לתאר את הבעיה \'חייבות\' לקבל ייצוג מתאים (מחלקות) במערכת\".
    אז הנה, קיצרנו לעצמנו מעט – ויש לנו נקודת התחלה כלשהי.
    מהם המלים שחוזרות על עצמן בתיאור הבעיה? \"מלכה\" ו\"לוח\" (לפחות אצלי):
    להבהיר: במיוחד במערכת כ\"כ קטנה, במיוחד כשכותב אותה אדם בודד, ובמיוחד בעולם האג\'ייל – אין הגיון לייצר תרשימי UML! יצרתי את התרשים רק עבורכם – קוראי הבלוג.
    האופן הטבעי לעשות זאת הוא לכתוב קוד מסגרת ראשוני (כלומר: skeleton):

    לגבי שפת רובי (הנה פוסט \"נחיתה\" – למי שלא מכיר), attr_accessor היא דרך מהירה לייצר getter/setters לרשימת ה symbols שהוצמדה לפונקציה. למשל, במקרה של המחלקה Board ייווצר משתנה בשם queens@ שערכו nil, מתודה בשם queens שמחזירה את ערך המשתנה, ומתודה בשם =queens שמאפשרת לבצע השמה לערך בעזרת סימן \"=\" (להלן הסבר יותר מפורט)

    כבר ביצירת השלד הזה – בעצם לקחתי כבר כמה החלטות \"תכנוניות\" (להזכיר: קוד < תכנון < ארכיטקטורה – אך יש ביניהם דמיון רב).

    עם הניסיון אנו יודעים להחליט מהר יותר מה אנו מעדיפים, ואנו יודעים מהר יותר להסביר מדוע. כשהייתי מהנדס תוכנה צעיר – הייתי יכול להשקיע זמן רב בכדי להחליט כיצד אני רוצה לייצג את הדברים הבסיסיים ביותר. 
    אלו החלטות כבר \"לקחתי\"?
    דילמה: כיצד לייצג את הלוח והכלים עליו
    • אופציה א\': לוח כמטריצה 8×8 של מערך המחזיק אובייקטים.
      • יתרונות: מודל פשוט וקל להבנה
    • אופציה ב\': ניהול רשימות של הכלים השונים – כאשר כל כלי מחזיק את המיקום שלו על הלוח.
      • יתרון: יותר יעיל (טיול על x כלים – שזה מספר קטן מ 64 תאים – בכדי למצוא כלי) – לא משמעותי כאן.
      • יתרון: מישהו יצטרך לחשב \"איום\" של כלי על כלי אחר. זה גורר דילמה אחרת (מייד) – שאת התוצאה שלה, אופציה זו \"תקבל\" בצורה טבעית יותר.
    לכן בחרתי – באופציה ב\'. עד כמה ההחלטה קריטית? – לא ממש. אם המפתחים איתם אעבוד יעדיפו את אופציה א\' – אזרום איתם ללא נקיפות מצפון.
    דילמה (forward thinking): כיצד מחשבים איומים של כלי אחד על כלים אחרים
    קשה להתקדם ביישום פתרון לבעיה – מבלי שהנקודות המפתח ברורות, והנה אחת כזו.
    • אופציה א\': הלוח הוא זה שמחשב איומים בין הכלים
      • יתרונות: אני מעריך שאופציה זו תדרוש פחות שורות קוד.
    • אופציה ב\': כל כלי הוא זה שמחשב את האיומים שלו
      • יתרונות: יותר OO, משום ש\"הצלחנו למצוא\" מומחיות למחלקות. מלכה מומחית בלהכיר את האיומים שהיא מפיקה, ופרש מומחה בלהכיר את האיומים שהוא מפיק.

    תהליך העיצוב והארכיטקטורה הוא תהליך של יצירת סדר. מחלקות לא מקבלות אחריויות משמיים – הן מקבלות אותן מאיתנו. האחריות עוזרת לקבוע גבולות שמשמרים את הסדר. אם אתם קוראים את הבלוג תקופה – אני מניח שרעיונות אלו כבר מוכרים. אני \"טוחן\" אותם פעם נוספת מכיוון שהם חשובים מאוד – ואני נתקל שוב ושוב באנשים שהרעיונות עדיין לא ברורים להם.

    אם היינו מתמודדים עם בעיה דמיונות בשם \"8 המלכות\" [ב] בה יש רק מלכות – אולי היה היתרון של אופציה ב\' היה פחות ברור. אני חושב שאני אישית עדיין הייתי מעדיף לבחור באופציה ב\' – כי אני לא מרגיש עקרונית נוח עם \"לוח יודע כל\".
    בקיצור: אני בוחר באופציה ב\' – וזו בחירה משמעותית, שתשפיע על כל עיצוב המערכת. אני לא אומר שאם מפתחים שעובדים איתי היו רוצים את אופציה א\' הייתי טוען שאופציה א\' היא לא אופציה סבירה. אבל לבחירה הזו תהיה השפעה ישירה ועקיפה על הרבה בחירות אחרות במערכת שלנו – בחירות שלא יהיה ניתן להפוך בקלות (כלומר: ללא שינוי של הרבה שורות של קוד). זוהי דוגמה לבחירה בעלת משמעות ארוכת-טווח.
    הנה עדכנתי את התוכנית בהשלכות של הבחירה באופציה ב\':

    למי שלא מכיר UML – חץ משמעו קשר, שמתבטא בפועל ב member של המחלקה. כלומר: חץ דו-כיווני אומר שבמחלקה Queen יש member בשם board@, ובמחלקה Board יש מערך של members בשם queens@.
    יש לנו כאן קשר דו-כיווני, מה שאמור להדליק לנו נורה אדומה. ככל האפשר (עקרונות ה OO) – מומלץ להפחית קשרים דו-כיווניים (וקשרים בכלל) מהמערכת.
    בואו נזכר מדוע אנו רוצים את הקשרים האלו:
    • מלכה מכירה את הלוח – כי היא צריכה לבדוק מה יש בתאים מסביבה – (\"()board.content_at\").
    • לוח מכיר את המלכות – כי הוא מציב את המלכות.
    הממ… האם אפשר \"לפטור\" את עצמנו מאחד מהקשרים? נראה שכן. אפשר שהלוח לא יהיה אחראי להצבה (הנה הגדרת אחריות). נגדיר מחלקה אחרת, BoardManager (שם מקומם משהו) שתיקח ממחלקת ה Board את האחריות ואת התלויות שבאות איתה.
    בואו נכסה עוד יכולת נדרשת לתכנון: היכולת להדפיס את הפתרון (כלומר: מצב הלוח בו 7 מלכות ופרש לא מאיימים זה על זה) על המסך. לצורך המערכת הזו – מדובר בהדפסת ASCII ב console.

    דילמה: כיצד לבצע את ההדפסה:

    • אופציה א\': ה Board אחראי להדפסה
      • ייתרון: יש למחלקה את כל המידע הדרוש להדפסה
      • חיסרון: עכשיו יהיו למחלקה 2 אחריויות: א. לנהל את ה state של פתרון, ב. לנהל את ההדפסה של ה state הזה. 2 אחריויות היא חריגה מה SRP (קיצור של: Single Responsibility Principle).
    • אופציה ב\': ה Board מדפיס את הלוח, ועושה to_s (כלומר: toString ברובי) למלכה – כדי לתת לה את האחריות כיצד להציג את עצמה (למשל: האות \"Q\" או סמיילי)
      • ייתרון: פיזור אחריות בין אובייקטים שאחראים למשימה (scalability פיתוחי)
      • חיסרון: פיזור אחריויות – אין SRP
    • אופציה ג\': ליצור מחלקה נוספת שאחראית על ההדפסה
      • ייתרון: הפרדת אחריויות ברורה בקוד
      • חיסרון: מה השתגענו – עוד מחלקה? כלומר: יותר קוד לכתוב
    הדבר הראשון שיש לשים לב אליו במקרה זה הוא שיש לנו 3 אופציות: אופציה א\' ו ב\' הן יחסית דומות בעוד אופציה ג\' שונה. במקרה שכזה כדאי שתידלק לנו נורה אדומה (קטנה) בשל זיהוי דפוס קבלת החלטות שנקרא \"A, A-, B\".

    מה זה אומר? באופן טבעי כבני אדם, אנו נוטים להעדיף השוואות נוחות.
    קל יותר להשאוות בין אופציה A לאופציה -A שדומה לה, אך פחות טובה ממנה ולבחור את A, מאשר להחליט בין A ל B.
    לכן, אנו נוטים \"לזנוח\" את B ולהתמקד בהשוואה בין 2 האופציות הנוחות בלבד.

    כדי לקבל החלטה טובה יותר, כדאי לזהות את המצב ולבצע את ההחלטה בשני שלבים:

    1. שלב א\': A מול A-, ולהעלות את אופציה A \"לגמר\".
    2. שלב ב\': לבחון, מחדש, את אופציה A מול אופציה B.
    אני חייב להודות שבמקרה שלנו זה סוג של אופציה A1 ו A2 – כי קשה לי לומר בהכרח מי עדיפה מבין אופציה א\' וב\'.
    אבל, אם אתם בשלב תכנון ועולות 2 אופציות דומות שאחת נראית בבירור פחות טובה – הסירו אותה. היא נותנת הרגשת ביטחון באופציה הדומה לה – על חשבון אופציות אמיתיות אחרות
    אם אתם מעוניינים להעביר החלטה \"בלי שקולגות יפריעו לכם\" (לא יפה!!) – צרו אופציה A- מגוחכת, בכדי להעלות את סיכוייה של אופציה A להיבחר. עוד פרטים על כך אפשר לקרוא בפרק הראשון בספרו של דן אריאלי \"לא רציונלי, ולא במקרה\".
    חזרה אלינו:
    אנו רוצים לקבל באמת את ההחלטה הטובה ביותר, ולכן הסרתי את אופציה ב\' שנראית לי פחות מאופציה א\'. ולכן:
    • אופציה א\': ה Board אחראי להדפסה
      • ייתרון: יש את כל המידע הדרוש להדפסה
      • חיסרון: עכשיו יהיו לו 2 אחריויות: א. לנהל את ה state של פתרון, ב. לנהל את ההדפסה של ה state הזה. 2 אחריויות היא חריגה מה SRP (קיצור של: Single Responsibility Principle).
    • אופציה ב\' (החדשה): ליצור מחלקה נוספת שאחראית על ההדפסה
      • ייתרון: הפרדת אחריויות ברורה בקוד
      • חיסרון: מה השתגענו – עוד מחלקה? כלומר: יותר קוד לכתוב

    בדילמה ביניהן – אני בוחר באופציה ב\'. 
    אופציה א\' היא מהירה יותר למימוש, אבל אני אישית מרגיש יותר נוח לתקתק קצת יותר – ולבנות מבנה שהוא יותר scalabale לעתיד.

    מצד אחד, זוהי אולי \"שריטה של ארכיטקט\". המערכת שלנו הולכת להיות קטנה למדי (אין לה \"עתיד\") – והקוד יהיה ניתן לניהול גם ללא קנאות ל SRP. זהו Waste.
    מצד שני, זהו הרגל טוב למערכות גדולות – והרגלים לא מחליפים בקלות כמו גרביים. אני מעדיף לחיות עם אינטואיציה שהיא פחות יעילה למערכות קטנות, אך יותר יעילה למערכות בינוניות / גדולות (שם אני מבלה את רוב זמני).
    אני בוחר באופציה ב\' והמבנה שלנו כעת נראה כך:
    ארכיטקטורה א\'
    זהו, האם הגעתי לארכיטקטורה המושלמת לבעיה הנתונה?
    אז זהו… בוודאי שלא!
    1. אנו לא בעסקי המושלמות (לפחות לא אני). המטרה היא לייצר ארכיטקטורה \"טובה\" (ככה \"top 5\") ולא \"הטובה ביותר\". למה? כי זה פשוט בלתי-אפשרי לוגית. כל דרישה חדשה למוצר הופכת את הסדר בין כמה הארכיטקטורות הטובות ומציבה ארכיטקטורה אחרת בראש הטבלה כ\"ארכיטקטורה הטובה ביותר\". מכיוון שאי אפשר לחזות את העתיד – אז אי אפשר גם להעריך איזו ארכיטקטורה, מבין הארכיטקטורות \"הטובות\" היא הטובה ביותר. פשוט אי אפשר.
    2. ארכיטקטורה מתאימים להקשר מסוים: מי הצוות שיעבוד איתה, מי הלקוח, ומה הסיטואציה (לחץ, פרויקט אסטרטגי, וכו\'). לא יהיה נכון להשוות ארכיטקורות שונות כאשר יש להן הקשרים שונים.
      למשל: בארכיטקטורה הזו אני עומד להיות המפתח. מה שטוב עבורי, לא בהכרח מספיק טוב לכל אדם אחר.
    3. את הארכיטקטורה ניתן באמת להעריך רק לאחר זמן ממושך. דברו איתי עוד חודשיים – ואספר לכם כמה טובה היא הייתה.
      כלומר: מדובר בהימור מחושב.
    ברגע שהתגבש מבנה בסיסי, זוהי נקודת זמן טובה להשוות אותו למבנה אחר.
    מה קרה? לקחנו סט של בחירות במהלך הדרך – והשוונו בחירה מול בחירה, אך לא תמונה מול תמונה.
    ייתכן שלאור הבחירה הראשונה שביצענו – אכן בחרנו בחירות טובות. אבל, אם הבחירה הראשונה הייתה אחרת – היינו מגיעים לסט בחירות שסה\"כ הוא מוצלח יותר.

    בואו נבנה מבנה הגיוני אחר (או שניים) – ונבחן אותם. נראה אם אנו יכולים להגיע לפתרון שהוא סה\"כ טוב יותר.
    הנה אלטרנטיבה אחרת (להלן ארכיטקטורה ב\'):

    ארכיטקטורה ב\'

    היתרונות של ארכיטקטורה א\' הם:

    • הפרדת אחריויות מקיפה (כל מחלקה אחראית על משהו אחד)
    • מוכנות לגדילה* (development scalability). 
    * מדוע מוכנות לגדילה היא ייתרון? תמיד הרי נוכל לעשות refactoring למבנה הרצוי, ברגע שנזדקק לו בפועל.
    המוכנות לגדילה ייתרון במידה ויש עוד מפתחים שעובדים על הפתרון ולא בקיאים בשיקולים כמו המתכנן. ע\"י בניית \"תשתית רחבה יותר\" המתכנן יכול להכווין את המפתחים הנוספים לכיוון הרצוי.
    עבורי אישית זה עניין של הרגל / שלווה פנימית.
    היתרונות של ארכיטקטורה ב\' הם:
    • מעט מחלקות – מעט קוד -> יותר מהר לפתח. בד\"כ עוד מחלקות דורשות כתיבה של עוד \"Gluing Code\".
    • הפרדה בין אחריויות – אם כי פחות מקיפה.
    אין פה עניין של ארכיטקטורה \"נכונה\". שתי הארכיטקטורות סבירות, ומה שנותר לנו הוא שקלול תמורות (trade-off) – איזו ארכיטקטורה נראית לנו מתאימה יותר בהקשר הנתון. בארכיטקטורה, בוחנים trade-offs כל הזמן.
    אני – בוחר בארכיטקטורה הראשונה. אני מרגיש פחות נוח עם מחלקת Board שאחראית גם על ניהול מצב, גם על הדפסה, וגם על הכרות עם הכלים השונים.

    סיכום התהליך

    בפוסט הזה, בעצם ביצענו תהליך של הגדרת ארכיטקטורה – אפילו שזה היה מאוד \"בקטן\".

    התהליך עצמו הוא דיי פשוט, ומבוסס על כמה עקרונות בסיסיים:

    1. הידע על הארכיטקטורה איננו קיים – הוא מתגלה. אנו מגלים תוך כדי תכנון ומעט קידוד (\"spike solutions\" ו/או POCs) תובנות חדשות, ומשלבים אותן בארכיטקטורה.
      נכון, שאם בנינו מערכות דומות בעבר – אנו יכולים להשתמש בידע שנצבר שם בכדי לקצר תהליכים.
    2. אפשרויות בחירה הן קריטיות לתכנון איכותי. ההרגל נוטה למשוך אותנו ל\"פתרון הראשון שיכול לעבוד\" ולהישאר שם, אבל חשוב לעצור לרגע וליצור עוד אלטרנטיבות ישימות – ואז לבחור מביניהן את הטובה יותר.

      כקוריוז: שימו לב שזה אחד הנושאים עליהם מבקר המדינה מעיר שוב ושוב לממשלה: לא הוצגו חלופות (ישימות) בפני חברי הממשלה – ולכן נבחרה החלופה היחידה שהוצגה. אני מניח שהעקרונות לשיטה הם דומים.

    3. ידע תאורטי מוקדם (SOLID, אחר) מסייע לנו לזהות \"נקודות בעייתיות\" – לבחון אותן, ולתקן אותן (במידת הצורך). יש הרבה מאוד כללים מנחים בעולם התוכנה לגבי תכנון מערכת – חלקם אפילו סותרים.

      למשל: בארכיטקטורה א\' הצבנו את כל אחריות ההדפסה על הלוח. בגלל ריבוי הכלים (מלכה, פרש) הוא יאלץ לבצע if (או case) ולהגדיר התנהגויות לכל כלי (המקביל לקריאה ל ()to_s בארכיטקטורה ב\'). מצב כזה נחשב כ bad smell בו מומלץ לעשות refactoring לכיוון State Pattern. מצד שני – המצב שייווצר יפגע ב SRP. פה יש עניין של הבחנה עיקר וטפל – שמבוסס במידה רבה על ניסיון מכיוון שאין דרך מתמטית \"להוכיח\" איזה מצב הוא עדיף. ניסיון הוא בסהכ ה\"מושכל\" ב\"הימור מושכל\", כמובן.

    זהו, בחמש דקות – עיקרו של תהליך הגדרת הארכיטקטורה.

    סיכום

    לא הספקנו ליצור ארכיטקטורה גדולה.
    לא הספקנו לגעת כמעט בקוד רובי (שבשבילו בעצם התחלתי את הפוסט… והדברים התגלגלו).
    לא הספקנו להתייחס בכלל לפרש. זו הייתה החלטה מודעת: בהגדרת ארכיטקטורה כדאי לצמצם \"רעשי רקע\". הדרישה לפרש הייתה במודעות ובחשיבה – אך זה הספיק ולכן לא הרגשתי צורך לפרוט אותה לפרטים.

    מצד שני:
    הספקנו לתאר בצורה דיי פשוטה את עיקרי תהליך הארכיטקטורה.
    אני מאמין שהפוסט מספק כמה תובנות משמעותיות, ולכן אני מרגיש נוח לעצור בנקודה זו. אולי אמשיך בעתיד אם ארגיש שהמשך העבודה שלי על התרגיל היא קרקע פוריה להצגת תובנות נוספות.
    אולי אפילו הצלחתי קצת להעמיס ולבלבל עם כל ה\"התפלספויות\". אני רק מנחש שהפוסט עשוי להיות לא כל-כך קל לקריאה למהנדסים צעירים.

    שיהיה בהצלחה!

    —-
    [א] מלכה ברבים, מלכות, יש לקרוא כמְלָכוֹת (כ\' רפה) – כמו \"MELAHOT\"
    [ב] \"8 המלכות\" זו כמובן הבעיה המקרית. אני רציתי לגוון ולנסות משהו טיפה אחר.

    GOTO; Berlin 2014 – רשמים נוספים

    הנה תקציר של עוד כמה sessions מעניינים מהכנס:

    שימוש ב Lean UX בחברת Carbon Five

    Carbon Five זו חברת תוכנה (נחשבת?) שעובדת על פרויקטים ולא מוצרי מדף. הם אימצו וערבבו את מתודולוגיית ה Lean UX עם Agile Development, שהתרחבו לווריאציה משלהם.

    החברה מספקת שירותים רחבים מניהול מותג, פיתוח תוכנה, UX / visual design, ועד ל Operations של הפתרון.

    • עקרון תרבותי חשוב אצלם היה לשבור את ה \"מסגרות התפקיד\". עדיין יש PO, יש מעצב, ויש מתכנת. אבל מעצב, אם הוא מרגיש נוח עם CSS (בערך כ 50% אצלם הם כאלה) – יכול לדחוף שינויים ב CSS ישירות ל Git.
      PO יכול לפתח, ומפתח יכול להציע שיפורי UX. כל אחד מוזמן לתרום בכל מקום בו הוא יכול – תוך שכללי הארגון מעודדים עובדים \"לצאת\" ממסגרת הגדרות התפקיד. כמובן שבעלי התפקיד נושאים באחריות, ונותנים את הטון הסופי.
      איך זה עובד בפועל? האם זה יותר PR ממציאות? – אין לי מושג.
    • הם עובדים בספרינטים של שבוע, וכחלק מה Lean UX, עושים בדיקות שימושיות למוצר – כל שבוע. \"הרבה בדיקות, על קהל מצומצם\".
      • למרות שעושים בדיקות על קהל מצומצם (3-4 אנשים כל פעם), מנסים להגיע לקהלים שונים (רופאים, אחיות, פקידים, ורופאים בכירים ועצבניים – עבור מערכת ביה\"ח, למשל).
    • מה עושים שעדיין אין מספיק \"בשר\" במוצר בכדי לבצע בדיקות שימושיות? כלומר: בחודשים הראשונים של הפיתוח? מציירים על קיר מחיק או על כרטיסיות את ה UX המתוכנן ונותנים למשתמשים \"ללחוץ בכאילו\" על הכפתורים.
    • עושים על כל מסך הרבה איטרציות של שימושיות (measure), הפקת לקחים (learn), ויישומם (build). 
    • ה UX בד\"כ נמצאים שבועיים לפני המפתחים, ברמת התוצרים.
    • הם לא מאמינים ב\"מעצב הגאון\". מספרים (ממקור אחר) שאפל יישמה כל מסך או פיצ\'ר 9 פעמים, ובחרה מבין התוצרים את זה שהוכח כמוצלח ביותר – להיות זה שישוחרר בגרסה.
    • מפתחי UI עושים Pairing (כמו \"Pair Programming\") עם מעצב בזמן שמסדרים את ה UI. המעצבים מבינים טוב יותר את המגבלות של התוכנה – ויכולים לספק פתרונות עיצוביים במקום (מבלי שהמפתח יחכה להם). נשמע טוב!
    • קונפליקטים בצוותים הם חיוניים ל Innovation – אל תנסו \"להעלים\" אותם.
    • מצהירים שהתרבות הייחודית שיצרו, היא היתרון התחרותי העיקרי שלהם – מול מתחרים בתחום.
    Prototype מוקדם של Flow – כאשר הנבחן \"לוחץ\" על כפתור, ואז עובר (לאורך הקו) לסט הכפתורים הבא לבחירה. לא בוחנים מסכים שלמים, אלא רק את ה flow.

    הנה מבחן קטן: זהו את מתודולוגיות הפיתוח ע\"פ הצללית:

    (לחצו להגדלה)

    תשובות [א]

    Lean Enterprise

    כן – זה קורה: Jez Humble הולך לפרסם סופסופ את הספר The Lean Enterprise ב-15 בינואר 2015. עובדה: הוא כבר עסוק בקידום המכירות.

    אל תבלבלו עם הספר השחור (בעל אותו שם. יש שיאמרו: חיקוי).

    אמרנו ש Jez הוא בחור משעשע? הנה כך הוא פותח את ההצגה: כיצד מנהלים בכירים בארגונים בוחרים איזה מוצר לבנות?

    ע\"פ הסקר הם בעיקר עושים זאת ע\"פ תחושת הבטן הממוצעת (47%), ולא ע\"פ נתונים מדויקים (24%). התשובה השלישית (ע\"פ דעתו של בעל המשכורת הגבוהה ביותר), כך טוען ג\'ז, נוספה בכדי להוסיף קצת הומור לשאלון – אך עדיין 13% מהמנהלים הבכירים בחרו בה.

    מה הבעיה עם תחושת בטן – אתם שואלים?

    רון כוכבי (מסתבר שהוא מדען-נתונים מוערך בקנה מידה עולמי) הוביל את ה A/B Testing עבור אמזון במשך שנים, עד שמיקרוסופט \"רכשו\" אותו, בכדי לעשות אותו הדבר, עם 70 מדענים, בבינג.

    “Evaluating well-designed and executed experiments that were designed to improve a key metric, only about 1/3 were successful at improving the key metric!” — Online Experimentation at Microsoft, Kohavi et al 

    כוכבי היה אחד מאלו ש\"חיו\" על הנתונים של בדיקות A/B Testing יום-יומיות. אם יש דרך לאדם ללמוד מה הלקוחות רוצים / כיצד פיצ\'ר חדש ישפיע – אזי היא לחוות מאות ניסויים כאלו לאורך זמן.

    ומה מידת הדיוק, של אותם מומחים שאומנו היטב, לחזות הצלחה של פיצ\'רים במוצר? – כמעט 70% כישלון בחיזוי.

    כלומר: יש לנו תחושות בטן, רובן הגיוניות:

    • שכדור הארץ הוא שטוח – \"רואים\"
    • שהשמש סובבת סביב כדור-הארץ – \"רואים כל יום!\"
    • ושזבובים נוצרים מתוך בשר שנשכח ללא קירור (- נסו ותראו).
    אבל רק כאשר נעשה ניסויים מדויקים, נגלה שחלק גדול מתחושות הבטן האלו – הוא שגוי.

    זוכרים מה Lean Startup מהו \"ה waste הגדול מכל\"? – לא ישיבות, לא קוד כפול, וגם לא טיפול בבאגים.
    הבזבוז הגדול ביותר הוא לפתח פיצ\'ר שאף אחד לא ישתמש בו (או לחלופין: ע\"פ כוכבי, לא לשפר או אפילו להרע את המדד העסקי) – והיי, יש לנו סבירות של 70% להגיע לשם בעזרת תחושות הבטן שלנו.

    הנה דוגמה:

    מה משנה איזה צבע יהיה הכפתור?!

    ובכן, CareLogger גילו, בעזרת A/B testing, ששינוי הצבע של כפתור יחיד באתר שלהם הגביר את מספר המשתמשים בשירות ב 34% אחוז (מקור). כמה פיתוח מוצר בד\"כ עושה בכדי לצמוח ב 34%?

    נכון: זו דוגמה קיצונית.

    לכן, ובהתאם לזאת, ג\'ז מציע להשליך את ה Story Template המקובל ב Agile, כלומר:

    As a , I want so that .
    לטובת ה Story Template הבא:

    We believe that , will achieve .
    We will know we are successful when we see .

    שימו לב: אם יש פיצ\'ר שאתם לא יודעים כיצד למדוד ששיפר מדד עסקי כלשהו – עדיף שלא תפתחו אותו בכלל.
    עם סיכוי של 70% לטעות – פשוט עדיף לא לעשות כלום. בעצם: עדיף להתאמץ עוד קצת ולמצוא דרך למדוד אותו בכל זאת.

    עוד טיעון מרכזי (לא חדש בכלל, למי שמכיר את הספרים של Christensen Clayton) הוא שניתן לחלק את פיתוח המוצרים בתוכנה ל 3 טווחים:

    השיטות, הכישרונות, והתרבות הארגונים שנדרשת ל Horizon 1 (תחזוקה של מוצר קיים ומוכח) – שונה מהותית מאלו שנדרשים לפיתוח מוצרים חדשים לחלוטין (Horizon 3). זה ההסבר מדוע לסטארט-אפים יש DNA ארגוני שונה מאוד מ Enterprises.
    Enterprises היו פעם סטאראט-אפ שחי ב Horizon 3, הם הצליחו, התבגרו, ואז עברו ל Horizon 2, ו Horizon 1. אם לא היו עושים את ההתבגרות הזו – הם לא היו שורדים. אבל… הם בדרך מאבדים את היכולת לעשות בחזרה את מה שעושים ב Horizon 3.
    ג\'ז מציע שתהיה הפרדה ארגונית ברורה בין ה Horizons השונים: ניהולית, תקנים שונים, ותהליכים שונים. רק כך ארגונים גדולים יוכלו לחדש כמו Startups.
    שווה לציין שקלייטון, שנחשב להוגה הדעות המשפיע ביותר בעולם בתחום העסקי, טוען שהפרדת בתהליכים לא מספיקה. שזה חייב להיות בניין אחר, תקציב אחר, וכו\' – עדיף חברת-בת עם שם אחר שנמצאת רחוקה פיסית מהחברה המרכזית.

    נושא מעניין נוסף היה תוצאות מחקר \"State Of DevOps 2014\".

    המחקר טוען להוכחת קשר סיבתי בין ביצועי ה IT לביצועי הארגון (עסקית). קשר כזה נשמע הגיוני – אך רק פעמים בודדות טענו שהצליחו באמת להוכיח אותו. הקושי להוכיח זאת משמר את המצב בו IT נתפס ב\"מרכז עלות\" ולא כשותף אסטרטגי להצלחה העסקית (הכוונה לחברות שאינן חברות תוכנה).

    את \"ביצועי ה IT\" הם מדדו כשילוב של:

    • Throughput
      • זמן לפיתוח פיצ\'ר: הגדרה עד שחרור (קטן ככל האפשר).
      • תדירות שחרור גרסאות (deploy של גרסה חדשה כל 10 שניות כמו אמזון – זה פשוט מעולה)
    • Stability
      • Time to restore service – זמן להתאוששות מתקלה, ציינתי את המדד קודם לכן בפוסט.
      • אחוז השינויים שגורמים לתקלות (קטן ככל האפשר).

    באופן לא-טריוואלי, מדדים אלו הם משלימים ולאו דווקא סותרים. כלומר: במקום שיהיו ארגונים שטובים בא\' או טובים בב\', יש ארגונים שטובים בשניהם או שלא טובים באף אחד מהם.

    אם מודדים את ה IT באופן הנ\"ל – ניתן לראות קשר חזק ואפילו הוכחת סיבתיות (לא בחנתי את הדו\"ח בעצמי) בין ביצועי ה IT לביצועים הכספיים של החברה.

    כיצד משיגים את המדדים הללו?

    התגלה במחקר קשר ברור (correlation) בין ההצהרות הבאות – לביצועי IT:

    • \"את התקלות במערכת מגלות מערכות ה monitoring, לא משתמשים\"
    • \"כל המפתחים מבצעים merge ל trunk/master branch – כל יום\" (כלומר: by-the-book CI)
    • \"כאשר מפתחים ו DevOps משתפים פעולה – התוצאה היא בדכ win/win\".
    • \"המפתחים נוהגים לשבור פיצ\'רים גדולים לחתיכות קטנות שנכנסות למערכת בזו אחר זו\"
    ההתנהגויות הבאות הם המנבאים הטובים ביותר לביצועי IT:
    • יש peer review לכל קוד שנכנס למערכת המרכזית (בניגוד לחוסר בתהליך, או לסירוגין לתהליך \"כבד\" יותר מ peer review) 
    • כל הקונפיגורציות של המערכת (\"כל מה שאפשר\") מנוהל ב Source Control.
    • monitoring פרואקטיבי למערכות (למשל: הרצה של flows יזומים ובדיקה שלהם, ולא רק בדיקת מדדים טכניים של השרת).
    • שיתוף פעולה טוב בין Development ו Operations.

    לבסוף, היה גם עניין של ה Toyota Kata – שלא הצלחתי ממש לעקוב אחריו. תאלצו לקרוא את הספר, אם אתם רוצים לדעת במה מדובר 🙂

    ה Keynote של היום השני: פילוסוף גרמני נוזף

    (הערה: שתי ההרצאות הנ\"ל היו ביום השני)

    אולי גרמנים יזהו את השם Gunter Dueck – אני בהחלט לא זיהיתי. הציגו אותו כפילוסוף, מתמטיקאי, ואחד מ 100 הסופרים הנחשבים בגרמניה. הממ… והוא היה גם בכיר ב IBM גרמניה עד לפני כ 3 שנים (נראה שעסק בפיתוח של DB2 ומערכות DW/BI).

    ההרצאה שלו הייתה כתב תוכחה חריף מול התרבות הגרמנית \"שהולכים להזיז לה את הגבינה\". למשל: תסריט בו תעשיית הרכב קורסת לטובת מכוניות של גוגל / טסלה = קריסת הכלכלה הגרמנית. הוא טען ששליש מהמשרות בגרמניה נובעות באופן ישיר ועקיף מתעשיית הרכב. אאוץ.

    אז מה הוא אמר?
    היא סיפר על חוויות מ IBM גרמניה. על כך שכל פעם שהיו מפספסים עסקה גדולה – היה מפגש של ההנהלה בשם Lessons Learned.

    \"מנהלים הם כמו כלבים – רק תנו להם להביא לכם מקל, מומחי תוכנה הם כמו חתולים – לעולם לא יהיו מרוצים\". מי אני שאתווכח עם פילוסוף גרמני?!

    מהר מאוד עלה דפוס שחוזר על עצמו: הלקוחות לא בחרו בפתרון של IBM כי הרגישו ש\"המתחרה היה מעוניין יותר בעסקה\" או שפשוט \"היה לו יותר אכפת\".

    וישלחו המנהלים את איש המכירות ללקוח לשאול: \"האם הפתרון שלנו לא היה טוב מספיק טכנולוגית?\" – \"לא, הייתה בד\"כ התשובה. הפתרון שלכם היה טכנית – הטוב ביותר\".

    \"ייישששש!!!!!!\" קראו בקול המנהלים ששמעו את הבשורה, מתעלמים מכך שבעצם הם הפסידו עסקה כשבידם הפתרון הטכני הטוב ביותר.

    וישלחו המנהלים שנית את איש המכירות ללקוח לשאול: \"אולי המחיר שלנו, של הפתרון הטוב ביותר, היה גבוה מידי?\" – \"לא, הייתה בד\"כ התשובה. המחיר היה תחרותי מאוד – אפילו בין הטובים\".

    וישלחו המנהלים שוב את איש המכירות לשאול: \"אז למה זה מגיע לנו?!\" – והתשובה כמעט תמיד הייתה \"האנשים הטכניים שבאו, לא נתנו לנו הרגשה שהבעיות שלנו חשובות להם. אנשי המכירות לא ניסו להבין אותנו. פשוט למתחרים היה אכפת מאיתנו יותר!\"

    וכך זה חזר במשך כמה שנים – מבלי שההנהלה למדה משהו מכל ה \"Lesson Learned\", שחזרו על עצמם באדיקות בערך פעמיים בכל חודש.

    משם גונטר החל לבצע מחקרים על העובדים ב IBM גרמניה:

    • הוא שכנע מהנדסים לעבור מבחן, שהוא בעצם מבחן אוטיזם. אדם ממוצע מקבל 15/50, אספרגר מאובחן מציון של 27/50 במבחן, והמהנדס החציוני ב IBM שנבדק קיבל 25/50 במבחן. כלומר: ב IBM גרמניה יש הרבה מהנדסים שסובלים (או לחלופין: נהנים) מאספרגר. לא לצחוק: אולי המצב בארץ לא מאוד שונה.
    • את המנהלים הבכירים העביר מבחן סטנדרטי לאיתור חוזקות (התכונות הבולטות אצלם). אין לי את השקף, אבל זה היה בערך כך (מתוך 4 קטגוריות של חוזקות):
      • רוב המנהלים היו בעלי חוזקה דומיננטית ל\"סדר\".
      • רוב המובילים הטכנולוגיים היו בעלי \"אינטלקט\". 
      • רק ל 2 מתוך כמאה בכירים – התכונה הדומיננטית הייתה אמפתיה.
    גורטר סיפר שבראש כל תעודה בביה\"ס בגרמניה, מציינים 4 ציונים מרכזיים על התלמיד:
    • כמה הוא מסודר (ע\"פ התיאור, זה לא נשמע כמו \"סדר ונקיון\" בארץ בו כולם קיבלו \"טוב מאוד\").
    • כמה הוא עובד קשה (יוצא מגדרו).
    • הנתהגות (עם ילדים אחרים).
    • <לא זוכר: אולי עד כמה הוא משתף פעולה עם המורים).
    גורטר סיפר שבבית שלו – אלו היו הציונים הנחשבים, יותר מהציונים של המקצועות.
    התזה שלו הייתה שמערכת החינוך הגרמנית מחנכת את התלמידים למצוינות, אולי אפילו אליטזם – בקו צר של יכולות שלא כולל יכולות חברתיות.
    הוא טען שגרמנים הם מנומסים ונחמדים לזולת מתוך שאיפה למצוינות, ולא בגלל שאכפת להם מהאחר. בפועל: לא אכפת להם, וחסרה להם אמפתיה.

    היכולות הטכניות הגבוהות של הגרמנים נשחקות (עוברות תהליך של Commoditization), וללא שינוי – גרמניה לא תמשיך להיות מה שהיא היום.

    לא שכ\"כ משנה לי גורל הכלכלה הגרמנית, אך זו הייתה הרצאה מרתקת ועוצמתית, ואני מקווה שהצלחתי להעביר חלק מהחוויה.

    הנה עוד כמה מסרים מעניינים שעלו במהלך הכנס, ממרצים שונים:

    • IoT (כלומר: Internet of Things) כבר כאן – הוא פשוט נמצא כרגע במוצרי High-End בלבד. המחירים יירדו – ואז הוא יחלחל לכלל תחומי החיים.
    • IoT הוא לא Stack טכנולוגי חדש, זהו האינטרנט – אבל עדיין יש פרוטוקולים חדשים כגון MQTT או CoAP.
    • שימוש במונח Water-Scrum-Fall, לתאר מצב בו בפיתוח יש איטרציות קצרות בעוד ה Product וה Operations עובדים ע\"פ איטרציות ארוכות – מצב שהוא בהחלט בגדר \"פספוס נפוץ\". המונח הוטבע ע\"י סוכנות Forrester.
    • אבטחה: יש כיום יותר פריצות למערכות On-Premises מאשר למערכות ענן.
      זוהי קורליציה ולא סיבתיות, כלומר: ייתכן וזה בגלל שב On-Premises יש יותר מידע שמעניין פורצים ועדיין לא עבר לענן.
    • Vert.x הוא Framework מגניב. בסשן שלו פשוט היה צפוף!
    • Netflix שחררה 4 פרויקטים כ Open Source. מסתבר שהם שחררו דיי הרבה בשנה האחרונה. להזכיר: Netflix היא אחד ה Unicorns הבולטים בעולם הענן.
    • Adrian Cockcroft הוא בחור רציני ומרשים. לא הכרתי אותו קודם לכן. התחלתי לעקוב (@adrianco
    • ההבדל בין מהנדס חכם למהנדס נבון: מהנדס חכם יודע כבר את הפתרון, מהנדס נבון מוצא אותו תוך כמה דקות בגוגל…
    • Rules ב JUnit – יכולים להיות שימושיים למדי!

    אחרית דבר

    זהו, היה כנס מרגש – והיה לא קל לסכם את הכל.
    כתיבת הפוסט החזירה אותי לכנס, לכל פרט ופרט והעצימה את החוויה.

    כשחזרתי מברלין למשרד, הבאתי (כמקובל) משהו מתוק שקשור למדינה שבה הייתי.
    שלחתי מייל לכולם: \"Back from Berlin – Milki on my desk\", וקיבלתי הרבה תגובות עם חיוכים / \"לייק\" / וכו\' – אבל המילקי לא נאכל. רק אחרי איזה ארבעה מפגשים משעשעים במסדרון קלטתי שאנשים אשכרה חושבים שאני מתבדח. כלומר – לא הבינו שבאמת הבאתי מילקי. מייל הבהרה ששלחתי אח\"כ – גרם לערימת המילקי להיעלם.

    הייתי בברלין, והבאתי מילקי. הכל עובדות.

    שיהיה בהצלחה!

    —–
    [א] והתשובות הן:

    א. Design Thinking
    ב. Lean Startup
    ג. Lean UX
    ד. Agile
    נכון, Lean UX ו Design Thinking הן ממוקדות UX ולא פיתוח. Agile זה בערך סקראם.