על מדיניות בניית 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” שאין בלינוקס. התחושה שלי היא שהתסריטים האילו כולאים את המתכנתים בתוך קופסה, וככל שהתסריטים עצמם קלים יותר, כך קשה יותר לצאת מהקופסה. אני משוכנע שיש אנשים שזה טוב להם, אבל אני, באופן אישי, אוותר.

שחר

מאת

שחר שמש

מייסד–שותף וחבר ועד בתנועה לזכויות דיגיטליות מייסד שותף בעמותת „המקור”. פעיל קוד פתוח. מפתח שפת התכנות Practical

20 תגובות בנושא “על מדיניות בניית API”

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

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

    1. למי אתה מציע את זה? למיקרוסופט? מה יצא להם מזה? יש להם אלפי "מתכנתים" שכלואים בקופסאות שנבנו ע"י מיקרוסופט, זה יופי למיקרוסופט.

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

      (שמות שקופצים לי ישר לראש הן ACE ו-Boost, אבל יש הרבה אחרות).

      הממשק היחיד שאני מכיר שהוא באמת cross-platform הוא POSIX, שזה בעצם מה שלינוקס מממש (ה"מימוש" של מיקרוסופט ל-POSIX הוא, למיטב הבנתי, רק כדי לצאת ידי חובה ולא משהו שימושי כל כך).

      1. אני מציע את זה למפתחי ה-Kernel של Linux ולאנשים כמו שחר שמש, שצריכים לעשות כל מיני פעולות לא סטנדרטיות עם המשאבים שמערכת ההפעלה מעמידה עבורם.

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

      2. גם ב-UNIX יש SYSTEM CALLS מורכבים שמבצעים כמה API פשוטים – לדוגמא ששחר נתן – יש popen שפותח צינור חד קיווני (קריאה או כתיבה), מבצע fork ומבצע פקודה ע"י shell (כלומר system) בבן. אני לא יודע אם לקרוא לזה API שכבה ראשונה/שניה/…, אבל זו פונקציה שנמצאת ב-sdtio (ספריה סטנדרטית לחלוטין).

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

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

      שחר

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

    1. בגלל ש:
      1. הקוד כרגע לא עובד כך, וזה יהיה הרבה יותר עבודה לגרום לו.
      2. אין לי כרגע את השינויים ל-gzip שמאפשרים לעשות לו rsync בקוד הספריה, רק בקוד ה-Standalone.

      בסופו של דבר הכוונה היא להשתמש בספריה גם ביוניקס, ולעבור להשתמש ב-threads במקום processes כדי לנצל יכולות מיקבול.

      שחר

  3. אני מתנצלת ….
    הגיון בלהשתמש בתוכנה מלאה ולא בסיפריה …די לא נראה לי מאורגן …כלל
    השיטה כמו שאמרו קודם זה גישה לספריה

    אתה מסבך דברים ….זה כלל לא קשור ל API ..אם זה היה מתוכנן נכון ..
    אם יש לך בעיה אם סינכרון בין שני תהליכים ..
    אין בעיה שתישלח הודעות בין שני התהלכים …

    בהצלחה ..
    אגב ה תרד….זה אולי חלק מאוד קטן מחבילת APII של וונדוס ..
    …אבל לא כדי לשפוט את כולו ..
    בהצלחה ..

    1. היי דניאלה.

      אני לא בטוח שהבנתי מה שהתכוונת להגיד, אבל אני אנסה לענות.

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

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

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

      אני מקווה שהבהרתי את עמדתי.

      שחר

      1. אממממ…..יכול להיות שיש צורך לבדוק
        איזה הודעות נישלחות

        מהתכונה החיצונית על ידי 32WINSHIGHT ..איזה יתן איזשהו דוגמה …למה מתכולל שם …יש אפשרות גם לדעת את מבנה ה DLL החיצוני …בעזרת תוכנית שונות שמאפשרים לראות איזה פונקציות יש שם …תן לי כמה זמן ואני ימצא באיזה תוכנה אני ישתמתשתי כדי לפצח את OUTLOOK EXPRESS ולהשתמש בכול הפיצרים מבלי לקבל את קוד המקור …

        אני חייבת להגיד ש ה API זה כלי מאוד חזק …שיכול לתת לך היסתכלות איךך המערכת המשומנת הזו עובדת ..גם מבלי לדעת את קוד המקור ..:)

        נכון שיש חסרונות ב ווינדוס …אבל אני חייבת לציין שזה לא אחד מהם ..

        זה אולי המנגנון היחיד שעובד בצורה מושלמת .ב וונדוס ..
        אני מאוד ישמח לעזור …אם אתה עדיין צריך עזרה ..

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

          1. כן לצערי ..אני מחוברת לרשת דרך PALM …הב WIFI ..לצערי המחשב שלי עלה באש לפני יומים ..(קצר חשמלי)

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

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

          שחר

    1. אני לא חושב שהבנת אותי נכון, לא.

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

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

      שחר

  4. OK
    נעשה קצת סדר בדברים .
    אם יש אפשרות לקבל את תמונה של המצב הנוכחי של הפרויקט.
    מכיוון של מה הרעיון הכללי .
    את מי רוצים להרוג
    . אני מניחה ששני התוכניות מנצלים את ה קונסול ולא ממשק גרפי .ואת הפלט שלו ?
    האם הדבר נכון ?
    השיטה ב WINDOWS ליצור PIPE בממשק של קונסול זה די אסון ..ולא מומלץ .
    אתה יכול לפרט יותר .
    או לתת דוגמה יותר פשוטה ?

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

      שחר

      1. סדר דברים מספר 2 ….
        לפי השקפות עולם שונות לחלוטין .
        נתחיל מהכיוון שונה כדי לתקוף את הבעיה
        1) ליצור ולבדוק אם יש אפשרות להשתמש ב DLL ידוע .
        2) אם אפשרי לא לגשת כלל ל API (בגלל שאתה שונא אותו)
        3) האם אתה רוצה פתרון זהה לשני מערכות ולכן יש צורך להגיע למכנה משותף נמוך יותר .(ממה שאתה רגיל)
        4) אם יש לך את ספצפקציות של המוצר אולי ניתן לעצב אותו בצורה שונה ויותר אלגנטית .

        1. הי דניאלה.

          תודה על הרצון הטוב, אבל אני חושש שלא הבנת אותי. זה לא שאני לא מסתדר עם ה-API של חלונות. למרבה הצער, יש לי הרבה נסיון איתו, ואני מצליח להוציא ממנו את התוצאות שאני צריך.

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

          שוב תודה,
          שחר

          נ.ב.
          בכתבה הרלוונטית ב-whatsup (http://whatsup.org.il/modules.php?op=modload&name=News&file=article&sid=4405), יש מישהו שמתנסח בדיוק כמוך, הן בסגנון והן בתוכן. אתם אחים?
          ש.

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

Bear