על מדיניות בניית API

תוכנות מחשב מבצעות את מה שהן מבצעות באמצעות פניות למערכת ההפעלה. מערכת ההפעלה חושפת פעולות לביצוע באמצעות “API”, או “Application Program Interface”.

וכאן נכנסת השאלה הגדולה. מה עדיף? API מתוחכמים ורבי יכולות, או API פשוטים?

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

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

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

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

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

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

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

בפרט, כחלק מהמרת rsyncrypto לעבודה על חלונות, אני צריך להריץ תוכנה אחרת, תוך שאני מפנה את היציאה של אותה תוכנה לתוכנה שלי. הדרך לעשות את זה היא באמצעות ייצירת pipe (צינור נתונים), וחיבורו ליציאה של התהליך לפני שהופכים אותו ל-gzip, התוכנה שאותה רוצים להריץ.

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

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

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

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

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

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

שחר

Bear