JavaRush /בלוג Java /Random-HE /מדריך לסגנון תכנות כללי
pandaFromMinsk
רָמָה
Минск

מדריך לסגנון תכנות כללי

פורסם בקבוצה
מאמר זה הוא חלק מהקורס האקדמי "מתקדם Java." קורס זה נועד לעזור לך ללמוד כיצד להשתמש ביעילות בתכונות Java. החומר מכסה נושאים "מתקדמים" כגון יצירת אובייקט, תחרות, סדרה, השתקפות וכו'. הקורס ילמד אותך כיצד לשלוט ביעילות בטכניקות Java. פרטים כאן .
תוֹכֶן
1. הקדמה 2. היקף משתנים 3. שדות מחלקה ומשתנים מקומיים 4. ארגומנטים של שיטה ומשתנים מקומיים 5. איגרוף ו- Unboxing 6. ממשקים 7. מחרוזות 8. מוסכמות שמות 9. ספריות סטנדרטיות 10. אי-שינוי 11. בדיקה 12. הבא. .. 13. הורד את קוד המקור
1. הקדמה
בחלק זה של המדריך נמשיך את הדיון שלנו בעקרונות הכלליים של סגנון תכנות טוב ועיצוב רספונסיבי בג'אווה. כבר ראינו חלק מהעקרונות הללו בפרקים הקודמים של המדריך, אך יינתנו עצות מעשיות רבות במטרה לשפר את כישוריו של מפתח Java.
2. היקף משתנה
בחלק השלישי ("כיצד לעצב מחלקות וממשקים") דנו כיצד ניתן להחיל נראות ונגישות על חברי מחלקות וממשקים, בהינתן אילוצי היקף. עם זאת, עדיין לא דנו במשתנים מקומיים המשמשים ביישומי שיטות. בשפת Java, לכל משתנה מקומי, לאחר שהוכרז, יש היקף. משתנה זה הופך לגלוי מהמקום בו הוא מוכרז עד לנקודה שבה מסתיימת ביצוע השיטה (או בלוק הקוד). בדרך כלל, הכלל היחיד שיש לפעול לפיו הוא להכריז על משתנה מקומי קרוב ככל האפשר למקום שבו הוא ישמש. תן לי להסתכל על דוגמה טיפוסית: for( final Locale locale: Locale.getAvailableLocales() ) { // блок codeа } try( final InputStream in = new FileInputStream( "file.txt" ) ) { // блока codeа } בשני קטעי הקוד, היקף המשתנים מוגבל לבלוקי הביצוע שבהם משתנים אלה מוצהרים. כאשר הבלוק מסתיים, ה-scope מסתיים והמשתנה הופך לבלתי נראה. זה נראה ברור יותר, אבל עם שחרורו של Java 8 והכנסת למבדות, רבים מהביטויים הידועים של השפה המשתמשים במשתנים מקומיים מתיישנים. הרשו לי לתת דוגמה מהדוגמה הקודמת באמצעות lambdas במקום לולאה: Arrays.stream( Locale.getAvailableLocales() ).forEach( ( locale ) -> { // блок codeа } ); ניתן לראות שהמשתנה המקומי הפך לארגומנט לפונקציה, שבתורו מועבר כארגומנט למתודה forEach .
3. שדות מחלקה ומשתנים מקומיים
כל שיטה ב-Java שייכת למחלקה מסוימת (או, במקרה של Java8, ממשק שבו השיטה מוכרזת כשיטת ברירת המחדל). בין משתנים מקומיים שהם שדות של מחלקה או מתודות המשמשות ביישום, קיימת ככזו אפשרות להתנגשות שמות. מהדר ג'אווה יודע לבחור את המשתנה הנכון מבין הזמינים, למרות שיותר ממפתח אחד מתכוון להשתמש במשתנה זה. מכשירי Java IDE מודרניים עושים עבודה מצוינת בלספר למפתח מתי עומדים להתרחש קונפליקטים כאלה, באמצעות אזהרות מהדר והדגשת משתנים. אבל עדיין עדיף לחשוב על דברים כאלה בזמן כתיבת קוד. אני מציע להסתכל על דוגמה: public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += value; return value; } } הדוגמה נראית די קלה, אבל היא מלכודת. שיטת calculateValue מציגה ערך משתנה מקומי ופועלת עליו מסתירה את שדה המחלקה באותו שם. השורה value += value; צריכה להיות סכום הערך של שדה המחלקה והמשתנה המקומי, אך במקום זאת, נעשה משהו אחר. יישום נכון ייראה כך (באמצעות מילת המפתח הזה): public class LocalVariableAndClassMember { private long value; public long calculateValue( final long initial ) { long value = initial; value *= 10; value += this.value; return value; } } אמנם הדוגמה הזו תמימה במובנים מסוימים, אבל היא מדגימה נקודה חשובה שבמקרים מסוימים זה יכול לקחת שעות כדי לבצע ניפוי ותיקון.
4. ארגומנטים של שיטה ומשתנים מקומיים
מפל נוסף שלעתים קרובות נופלים אליו מפתחי Java חסרי ניסיון הוא שימוש בארגומנטים של שיטה כמשתנים מקומיים. Java מאפשרת לך להקצות מחדש ערכים לארגומנטים שאינם קבועים (עם זאת, אין לכך השפעה על הערך המקורי): public String sanitize( String str ) { if( !str.isEmpty() ) { str = str.trim(); } str = str.toLowerCase(); return str; } קטע הקוד שלמעלה אינו אלגנטי, אך הוא עושה עבודה טובה בגילוי הבעיה: הארגומנט str מוקצה ערך שונה (ומשמש בעצם כמשתנה מקומי) . בכל המקרים (ללא כל יוצא מן הכלל), אתה יכול וצריך להסתדר בלי דוגמה זו (לדוגמה, על ידי הכרזה על הארגומנטים כקבועים). לדוגמה: public String sanitize( final String str ) { String sanitized = str; if( !str.isEmpty() ) { sanitized = str.trim(); } sanitized = sanitized.toLowerCase(); return sanitized; } על ידי ביצוע כלל פשוט זה, קל יותר להתחקות אחר הקוד הנתון ולמצוא את מקור הבעיה, גם כאשר מציגים משתנים מקומיים.
5. אריזה ופריקה
איגרוף ו-unboxing הוא שמה של טכניקה המשמשת ב-Java להמרת טיפוסים פרימיטיביים ( int, long, double, וכו' ) לסוגים מתאימים ( Intal, Long, Double וכו'). בחלק 4 של המדריך כיצד ומתי להשתמש בגנריות, כבר ראית את זה בפעולה כשדיברתי על עטיפת טיפוסים פרימיטיביים כפרמטרי סוג של גנריות. למרות שהמהדר של ג'אווה מנסה כמיטב יכולתו להסתיר המרות כאלה על ידי ביצוע אגרוף אוטומטי, לפעמים זה פחות מהצפוי ומפיק תוצאות בלתי צפויות. הבה נסתכל על דוגמה: public static void calculate( final long value ) { // блок codeа } final Long value = null; calculate( value ); קטע הקוד שלמעלה מתחבר מצוין. עם זאת, הוא יזרוק NullPointerException על הקו // блок שבו הוא ממיר בין Long ל- long . עצה למקרה כזה היא שרצוי להשתמש בטיפוסים פרימיטיביים (עם זאת, אנחנו כבר יודעים שלא תמיד זה אפשרי).
6. ממשקים
בחלק 3 של המדריך, "כיצד לעצב מחלקות וממשקים", דנו בממשקים ובתכנות חוזים, תוך הדגשה שיש להעדיף ממשקים על פני מחלקות קונקרטיות במידת האפשר. מטרת סעיף זה היא לעודד אותך לשקול תחילה ממשקים על ידי הדגמת זאת באמצעות דוגמאות מהעולם האמיתי. ממשקים אינם קשורים למימוש ספציפי (למעט שיטות ברירת מחדל). הם רק חוזים, וכדוגמה, הם מספקים הרבה חופש וגמישות באופן שבו ניתן לבצע חוזים. גמישות זו הופכת חשובה יותר כאשר היישום כולל מערכות או שירותים חיצוניים. בואו נסתכל על דוגמה של ממשק פשוט והטמעתו האפשרית: public interface TimezoneService { TimeZone getTimeZone( final double lat, final double lon ) throws IOException; } public class TimezoneServiceImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { final URL url = new URL( String.format( "http://api.geonames.org/timezone?lat=%.2f&lng=%.2f&username=demo", lat, lon ) ); final HttpURLConnection connection = ( HttpURLConnection )url.openConnection(); connection.setRequestMethod( "GET" ); connection.setConnectTimeout( 1000 ); connection.setReadTimeout( 1000 ); connection.connect(); int status = connection.getResponseCode(); if (status == 200) { // Do something here } return TimeZone.getDefault(); } } קטע הקוד שלמעלה מציג דפוס ממשק טיפוסי והטמעתו. יישום זה משתמש בשירות HTTP חיצוני ( http://api.geonames.org/ ) כדי לאחזר את אזור הזמן של מיקום ספציפי. עם זאת, בגלל החוזה תלוי בממשק, קל מאוד להציג יישום אחר של הממשק, באמצעות, למשל, מסד נתונים או אפילו קובץ שטוח רגיל. בעזרתם ממשקים עוזרים מאוד בעיצוב קוד שניתן לבדיקה. לדוגמה, לא תמיד מעשי להתקשר לשירותים חיצוניים בכל בדיקה, ולכן הגיוני ליישם במקום זאת יישום חלופי ופשוט ביותר (כגון stub): public class TimezoneServiceTestImpl implements TimezoneService { @Override public TimeZone getTimeZone(final double lat, final double lon) throws IOException { return TimeZone.getDefault(); } } ניתן להשתמש ביישום זה בכל מקום בו נדרש ממשק TimezoneService , ולבודד את סקריפט בדיקה מתלות ברכיבים חיצוניים. דוגמאות מצוינות רבות של שימוש יעיל בממשקים כאלה מובלעות בתוך הספרייה הסטנדרטית של Java. אוספים, רשימות, סטים - לממשקים אלה יש מספר יישומים שניתן להחליף בצורה חלקה וניתן להחליף אותם כאשר חוזים מנצלים את היתרון. לדוגמה: public static< T > void print( final Collection< T > collection ) { for( final T element: collection ) { System.out.println( element ); } } print( new HashSet< Object >( /* ... */ ) ); print( new ArrayList< Integer >( /* ... */ ) ); print( new TreeSet< String >( /* ... */ ) );
7. מיתרים
מחרוזות הן אחד הסוגים הנפוצים ביותר הן ב-Java והן בשפות תכנות אחרות. שפת Java מפשטת מניפולציות שגרתיות של מחרוזות רבות על ידי תמיכה בפעולות שרשור והשוואה ישירות מהקופסה. בנוסף, הספרייה הסטנדרטית מכילה מחלקות רבות שהופכות את פעולות המחרוזות ליעילות. זה בדיוק מה שאנחנו הולכים לדון בסעיף זה. ב-Java, מחרוזות הן אובייקטים בלתי ניתנים לשינוי המיוצגים בקידוד UTF-16. בכל פעם שאתה משרשר מחרוזות (או מבצע פעולה כלשהי שמשנה את המחרוזת המקורית), נוצר מופע חדש של המחלקה String . בגלל זה, פעולת השרשור יכולה להיות מאוד לא יעילה, ולגרום ליצירת מופעי ביניים רבים של המחלקה String (יצירת זבל, באופן כללי). אבל הספרייה הסטנדרטית של Java מכילה שתי מחלקות שימושיות מאוד שמטרתן להפוך את מניפולציית המיתרים לנוחה. אלה הם StringBuilder ו- StringBuffer (ההבדל היחיד ביניהם הוא ש- StringBuffer בטוח בשרשור ואילו StringBuilder הוא ההפך). הבה נסתכל על כמה דוגמאות של שימוש באחת מהמחלקות הללו: final StringBuilder sb = new StringBuilder(); for( int i = 1; i <= 10; ++i ) { sb.append( " " ); sb.append( i ); } sb.deleteCharAt( 0 ); sb.insert( 0, "[" ); sb.replace( sb.length() - 3, sb.length(), "]" ); בעוד ששימוש ב-StringBuilder/StringBuffer הוא הדרך המומלצת לתפעל מחרוזות, זה עשוי להיראות מוגזם בתרחיש הפשוט ביותר של שרשור שתיים או שלוש מחרוזות, כך שאופרטור התוספת הרגיל ( ("+"), לדוגמה: String userId = "user:" + new Random().nextInt( 100 ); לעתים קרובות האלטרנטיבה הטובה ביותר לפשט שרשור היא להשתמש בעיצוב מחרוזת וכן בספריית Java Standard כדי לסייע במתן שיטת עוזר סטטית של String.format . זה תומך בסט עשיר של מפרטי פורמטים, כולל מספרים, סמלים, תאריך/שעה וכו' (עיין בתיעוד ההפניה לפרטים מלאים) שיטת String.format String.format( "%04d", 1 ); -> 0001 String.format( "%.2f", 12.324234d ); -> 12.32 String.format( "%tR", new Date() ); -> 21:11 String.format( "%tF", new Date() ); -> 2014-11-11 String.format( "%d%%", 12 ); -> 12% מספקת גישה נקייה וקלה ליצירת מחרוזות מסוגי נתונים שונים. ראוי לציין שמחשבי Java IDE מודרניים יכולים לנתח את מפרט הפורמט מהארגומנטים המועברים לשיטת String.format ולהזהיר מפתחים אם מתגלים אי התאמה.
8. מוסכמות שמות
Java היא שפה שאינה מאלצת מפתחים לעקוב בקפדנות אחר מוסכמות שמות, אך הקהילה פיתחה קבוצה של כללים פשוטים שגורמים לקוד Java להיראות עקבי הן בספרייה הסטנדרטית והן בכל פרויקט ג'אווה אחר:
  • שמות החבילות מופיעים באותיות קטנות: org.junit, com.fasterxml.jackson, javax.json
  • שמות של מחלקות, ספירות, ממשקים, הערות כתובות באות גדולה: StringBuilder, Runnable, @Override
  • שמות של שדות או שיטות (למעט סטטי final ) מצוינים בסימון גמל: isEmpty, format, addAll
  • שמות קבועים של שדות סופיים סטטיים או ספירה הם באותיות רישיות, מופרדים בקווים תחתונים ("_"): LOG, MIN_RADIX, INSTANCE.
  • משתנים מקומיים או ארגומנטים של מתודה מוקלדים בסימון גמל: str, newLength, minimumCapacity
  • שמות סוגי פרמטרים עבור כלליים מיוצגים על ידי אות בודדת באותיות גדולות: T, U, E
על ידי ביצוע המוסכמות הפשוטות הללו, הקוד שאתה כותב ייראה תמציתי ולא ניתן להבחין בסגנון מספריה או מסגרת אחרת, וירגיש כאילו הוא פותח על ידי אותו אדם (אחת מאותן פעמים נדירות שבהן מוסכמות באמת עובדות).
9. ספריות סטנדרטיות
לא משנה על איזה סוג של פרויקט Java אתה עובד, הספריות הסטנדרטיות של Java הן החברים הכי טובים שלך. כן, קשה לא להסכים שיש להם כמה קצוות גסים והחלטות עיצוב מוזרות, עם זאת, 99% מהמקרים מדובר בקוד איכותי שנכתב על ידי מומחים. שווה לחקור. כל מהדורת Java מביאה תכונות חדשות רבות לספריות קיימות (עם כמה בעיות אפשריות בתכונות ישנות), וגם מוסיפה ספריות חדשות רבות. Java 5 הביאה ספריית מקבילות חדשה כחלק מחבילת java.util.concurrent . Java 6 הציגה תמיכת סקריפטים (פחות מוכרת) ( חבילת javax.script ) ו- API מהדר של Java (כחלק מחבילת javax.tools ). Java 7 הביאה שיפורים רבים ל- java.util.concurrent , והציגה ספריית I/O חדשה בחבילת java.nio.file ותמיכה בשפות דינמיות ב- java.lang.invoke . ולבסוף, Java 8 הוסיפה את התאריך/שעה המיוחל בחבילת java.time . Java כפלטפורמה מתפתחת וחשוב מאוד שהיא תתקדם יחד עם השינויים הנ"ל. בכל פעם שאתה שוקל לכלול ספרייה או מסגרת של צד שלישי בפרוייקט שלך, ודא שהפונקציונליות הנדרשת אינה כלולה כבר בספריות Java הסטנדרטיות (כמובן, יש הרבה יישומים מיוחדים ובעלי ביצועים גבוהים של אלגוריתמים שקודמים ל- אלגוריתמים בספריות הסטנדרטיות, אבל ברוב המקרים הם באמת לא נחוצים).
10. חוסר שינוי
חוסר השינוי לאורך המדריך ובחלק זה נשאר כתזכורת: נא לקחת את זה ברצינות. אם מחלקה שאתה מעצב או שיטה שאתה מיישם יכולה לספק ערובה לבלתי משתנה, ניתן להשתמש בה ברוב המקרים בכל מקום ללא חשש שתשתנה בו זמנית. זה יהפוך את חייך כמפתחים (ובתקווה לחיי חברי הצוות שלך) לקלים יותר.
11. בדיקה
הפרקטיקה של פיתוח מונע מבחן (TDD) פופולרי מאוד בקהילת Java, מה שמעלה את הרף לאיכות הקוד. עם כל היתרונות ש-TDD מספקת, עצוב לראות שהספרייה הסטנדרטית של ג'אווה כיום אינה כוללת שום מסגרת בדיקה או כלי תמיכה. עם זאת, בדיקות הפכו לחלק הכרחי בפיתוח Java המודרני ובחלק זה נבחן כמה טכניקות בסיסיות המשתמשות במסגרת JUnit . ב-JUnit, בעצם, כל מבחן הוא קבוצה של הצהרות לגבי המצב או ההתנהגות הצפויים של אובייקט. הסוד לכתיבת מבחנים מעולים הוא לשמור אותם פשוטים וקצרים, בודקים דבר אחד בכל פעם. כתרגיל, בואו נכתוב קבוצה של מבחנים כדי לוודא ש- String.format היא פונקציה מקטע המחרוזת שמחזירה את התוצאה הרצויה. package com.javacodegeeks.advanced.generic; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.equalTo; import org.junit.Test; public class StringFormatTestCase { @Test public void testNumberFormattingWithLeadingZeros() { final String formatted = String.format( "%04d", 1 ); assertThat( formatted, equalTo( "0001" ) ); } @Test public void testDoubleFormattingWithTwoDecimalPoints() { final String formatted = String.format( "%.2f", 12.324234d ); assertThat( formatted, equalTo( "12.32" ) ); } } שתי הבדיקות נראות קריאות מאוד והביצוע שלהן הוא מקרים. כיום, פרויקט Java הממוצע מכיל מאות מקרי בדיקה, המעניקים למפתח משוב מהיר במהלך תהליך הפיתוח על רגרסיות או תכונות.
12. הבא
חלק זה של המדריך משלים סדרה של דיונים הקשורים לתרגול התכנות ב-Java ומדריכים לשפת תכנות זו. בפעם הבאה נחזור לתכונות השפה, ונחקור את עולם ג'אווה לגבי חריגים, סוגיהם, איך ומתי להשתמש בהם.
13. הורד את קוד המקור
זה היה שיעור על עקרונות פיתוח כלליים מקורס Java Advanced. את קוד המקור לשיעור ניתן להוריד כאן .
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION