תכנון ספריית ג'אווה ידידותית למשתמש

1. סקירה כללית

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

  • מה זה עם כל השיעורים האלה "* שירות"?
  • איך אני מיישר את זה, זה לוקח יותר מדי תלות. מה זה "בְּרִיחַ“?
  • אה, הרכבתי את זה, אבל עכשיו זה מתחיל לזרוק IllegalStateException. מה אני עושה לא נכון?

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

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

אדגים את הרעיונות כאן באמצעות שתי ספריות: charles ו- jcabi-github

2. גבולות

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

2.1. קֶלֶט

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

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

כמו כן, עלינו תמיד להציע יותר מבנאי אחד, לתת למשתמשים חלופות. תן להם לעבוד עם שניהם חוּט ו מספר שלם או אל תגביל אותם ל- a FileInputStream, לעבוד עם InputStream, כדי שיוכלו להגיש אולי ByteArrayInputStream כאשר בדיקות יחידות וכו '

לדוגמה, להלן מספר דרכים בהן אנו יכולים ליצור נקודת כניסה ל- Github API באמצעות jcabi-github:

Github noauth = RtGithub חדש (); Github basicauth = RtGithub חדש ("שם משתמש", "סיסמה"); Github oauth = RtGithub חדש ("אסימון"); 

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

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

מנהל התקן WebDriver = FirefoxDriver חדש (); מאגר ריפו = InMemoryRepository חדש (); מחרוזת indexPage = "//www.amihaiemil.com/index.html"; גרף WebCrawl = GraphCrawl חדש (indexPage, מנהל התקן, דפוסי Ignored חדש (), ריפו); graph.crawl (); 

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

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

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

אם עדיין יש לך ספקות, נסה להגיש בקשות HTTP ל- AWS באמצעות aws-sdk-java: תצטרך להתמודד עם מה שמכונה AmazonHttpClient, המשתמש ב- ClientConfiguration איפשהו ואז צריך לקחת ExecutionContext איפשהו בין לבין. לבסוף, ייתכן שתוכל לבצע את בקשתך ולקבל תגובה אך עדיין אין לך מושג מהו ExecutionContext, למשל.

2.2. תְפוּקָה

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

הסתכל שוב בקוד שלמעלה. מדוע עלינו לספק יישום מאגר? מדוע השיטה WebCrawl.crawl () אינה מחזירה רק רשימה של רכיבי WebPage? ברור שזה לא תפקידה של הספרייה לטפל בדפים שנסרקו. איך זה בכלל צריך לדעת מה נרצה לעשות איתם? משהו כזה:

גרף WebCrawl = GraphCrawl חדש (...); דפי רשימה = graph.crawl (); 

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

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

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

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

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

3. ממשקים

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

לדוגמא, ב jcabi-github ספריה בכיתה RtGithub si היחידה שהמשתמש רואה בפועל:

Repo repo = RtGithub חדש ("oauth_token"). Repos (). Get (Coordinates new.Simple חדשים ("eugenp / tutorials")); נושא גליון = repo.issues () .create ("גיליון לדוגמה", "נוצר עם jcabi-github");

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

Repo repo = RtRepo חדש (...)

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

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

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

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

4. צדדים שלישיים

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

היה שקוף ככל האפשר, במידת האפשר אל תחייב ליישומים בפועל. הדוגמא הטובה ביותר שעולה בראש היא: השתמש ב- SLF4J, שהוא רק API לרישום - אל תשתמש ישירות ב- log4j, אולי המשתמש היה רוצה להשתמש בכריכים אחרים.

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

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

5. מסקנה

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

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


$config[zx-auto] not found$config[zx-overlay] not found