25 בדצמ׳ 2008

על הקשר שבין אסתטיקה לפונקציונליות

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

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

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

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





private static int sendAnEmail(
String from,
List recepients,
String subject,
String body,
List attachements,
boolean actuallySend) throws Throwable {
List parts = new ArrayList();
// ''Build a sub-part for each attachment''
for (Iterator i = attachements.iterator(); i.hasNext();) {
MimeBodyPart currPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource(i.next());
currPart.setDataHandler(new DataHandler(fds));
currPart.setFileName(fds.getName());
parts.add(currPart);
}
// ''Build the message. add sub-parts if exist''
Session ses = Session.getInstance(new Properties());
Message msg = new MimeMessage(ses);
if (parts.size() == 0)
msg.setText(body); // ''A single part message''
else {
// ''A Multi part message''
MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body);
Multipart mp = new MimeMultipart();
mp.addBodyPart(textPart);
for (Iterator i = parts.iterator(); i.hasNext();)
mp.addBodyPart(i.next());
msg.setContent(mp);
}
msg.setFrom(new InternetAddress(from));
msg.setSubject(subject);
msg.setSentDate(new Date());
Transport transp = ses.getTransport("smtp");
transp.connect(smtpServer, port, userName, password);
List toAddresses = new ArrayList();
for (Iterator i = recepients.iterator(); i.hasNext();) {
String curr = i.next();
InternetAddress[] temp = InternetAddress.parse(curr, false);
toAddresses.addAll(Arrays.asList(temp))
}
InternetAddress[] arr = new InternetAddress[toAddresses.size()];
int j = 0;
for (Iterator i = toAddresses.iterator(); i.hasNext(); ++j) {
InternetAddress curr = i.next();
arr[j] = curr;
}
msg.addRecipients(Message.RecipientType.TO, arr);
if (actuallySend)
transp.sendMessage(msg, arr);
transp.close();
return arr.length;
}

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





private static void sendEmail(
final String from,
final List to,
final String subject,
final String body,
final List attachments) throws Throwable {
final Session s = Session.getInstance(new Properties());
final Message m = new MimeMessage(s);
setHeaders(m, from, to, subject);
setContent(m, body, attachments);
sendMessage(m, s);
}

private static void setHeaders(
final Message m,
final String from,
final List to,
final String subject) {
m.setSentDate(new Date());
m.setFrom(new InternetAddress(from));
for (final String t : to)
m.addRecipients(Message.RecipientType.TO, InternetAddress.parse(t, false));
m.setSubject(subject);
}

private static void setContent(
final Message m,
final String body,
final List attachments) throws MessagingException {
if (attachments.size() == 0) { // ''No attachments to send''
m.setText(body);
return;
}
// ''Generate a multi-part message''
final MimeBodyPart textPart = new MimeBodyPart();
textPart.setText(body);
final Multipart mp = new MimeMultipart();
mp.addBodyPart(textPart);
for (final File f : attachments)
mp.addBodyPart(makeBodyPart(f));
m.setContent(mp);
}

private static MimeBodyPart makeBodyPart(final File f) throws MessagingException {
final MimeBodyPart $ = new MimeBodyPart();
final FileDataSource fds = new FileDataSource(f);
$.setDataHandler(new DataHandler(fds));
$.setFileName(fds.getName());
return $;
}

private static void sendMessage(final Message m, final Session s) {
final Transport t = s.getTransport("smtp");
t.connect(smtpServer, port, userName, password);
t.sendMessage(m, m.getAllRecipients());
t.close();
}




ואני שואל: היא לא פשוט יפה? אתם לא פשוט רוצים להתחתן איתה?!
אז מה היה לנו פה? לפנינו פישוט בסגנון ספרטני. זהו סגנון תכנות מינימליסטי הדוגל בצמצום מספר הפרמטרים לפונקציה, צמצום ה scope של כל משתנה, צמצום אורך שמות משתנים ופונקציות, צמצום מספר ה control-structures כדוגמת if ו for ועוד צמצומים מצמצומים שונים. המטרה היא בסופו של דבר ליצור קוד שהוא יותר קריא ובכך גם יותר קל להבנה ולתחזוקה. ניתן לקרוא עוד על תכנות ספרטני כאן.

בגוגל עושים ביג-דיל מהקריאות של הקוד. כל מהנדס חדש חייב לעבור מבחן קריאות (readability) בשפה שבה הוא כותב ואם לא יעשה כך לא יוכל להגיש קוד בעצמו. לכל שפה יש מדריך סגנון (style-guide) שמגדיר בדיוק איך צריכה להראות פונקציה, איך בוחרים שמות, איך כותבים תיעוד, איך קוראים למשתנים איזה רווח לשים אחרי ה if ואיזה אחרי הפונקציה ועוד. כל מהנדס חדש עובר את הזובור הזה של קריאות שלאחריו הוא מאשפז את עצמו מרצון במוסד הקרוב.
בהתחלה חשבתי שקריאות היא דרך מקורית (וגיקית) של הוותיקים לתפוס תחת על הצעירים, להשפיל אותם וללמד אותם את מקומם. היום אני חושב שזו דרך לתפוס תחת אבל יש בזה בכל זאת קצת תועלת. היום אני עושה בממוצע שני code-review ביום, כלומר קורא קוד של אחרים, נותן הערות ובסופו של דבר מאשר או מבקש תיקונים ואני בטוח שבזכות זה שכולם עברו את הזובור הזה של קריאות הרבה יותר קל לי לקרוא קוד של אחרים. אני לא צריך להתרגל לסגנון אחר ולפעמים רק תוך כדי רפרוף קל וראשוני על הקוד אני יכול למצוא שגיאות.
מסקנת ביניים: יש ערך לקוד קריא ויפה ולו בכך שלאחרים יהיה קל יותר לקרוא את הקוד ולשפצו.

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

איך יודעים שהקוד יפה? הרי יופי זה עניין של טעם, לא? והלא אנחנו אנשי המדע והכל צריך להיות כמותי ומדוייק.
בתכנות הספרטני המתואר למעלה יש מספר מטריקות המעידות על "כיעור" של הקוד והמטרה היא לצמצם אותן. יש גם כלים שמתריעים בפני שגיאות סגנון כגון lint. כיום להרבה מאוד שפות יש כלי lint משלהם. ל HTML יש את ה W3C Validator שבודק נכונות של קוד HTML. ב HTML יש קשר הדוק בין נכונות לאסתטיקה.
ואנקדוטה נוספת לפני סיום: אחד המרצים שלי בטכניון אמר שהבשורה הגדולה של שפות תכנות, הסיבה שאנחנו כבר לא מתכנתים בבינרי, היא ששפת תכנות היא הפשרה בין מכונה לאדם. כלומר התכנית צריכה להיות מובנת לא רק ע"י המכונה אלא גם ע"י האדם שכותב ובמיוחד זה שקורא אותה. לדוגמא, חבר לעבודה אמר לי ש perl היא שפת write only.
ואולי עוד אנקדוטה אחת ודי, לא מזמן חבר שלח לי את הפוסט המצויין הזה שאומר שאילו שפות תכנות היו דתות אז C היתה יהדות כיוון שהיא עתיקה ומאוד מוגבלת, ++C היה איסלם כיוון שהיא מעמיסה עוד חוקים ומגבלות על גבי C ו perl הייתה voodoo.

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

תגובה 1:

אנונימי אמר/ה...

כל הדוגמא שלך התפקששה כי אין אינדנט בבלוג...
חבל...