הקדמה
בעת יצירת חבילה ב-Go, המטרה הסופית היא בדרך כלל להפוך את החבילה לנגישה למפתחים אחרים לשימוש, בחבילות מסדר גבוה יותר או בתוכניות מלאות. על ידי ייבוא החבילה, קטע הקוד שלך יכול לשמש כבלוק בניין לכלים אחרים, מורכבים יותר. עם זאת, רק חלק מהחבילות זמינות לייבוא. זה נקבע על ידי הנראות של החבילה.
נראות בהקשר זה משמעו את מרחב הקובץ ממנו ניתן להתייחס לחבילה או למבנה אחר. לדוגמה, אם אנו מגדירים משתנה בתוך פונקציה, הנראות (היקף) של משתנה זה היא רק בתוך הפונקציה שבה הוגדר. באופן דומה, אם אתה מגדיר משתנה בחבילה, תוכל להפוך אותו לנראה רק לחבילה זו, או לאפשר לו להיות גלוי גם מחוץ לחבילה.
שליטה מדוקדקת ברמת הגישה לחבילות חשובה בעת כתיבת קוד יעיל, במיוחד כאשר מתחשבים בשינויים עתידיים שאתה עשוי לרצות לבצע בחבילה שלך. אם אתה צריך לתקן באג, לשפר ביצועים או לשנות פונקציונליות, תרצה לבצע את השינוי באופן שלא יפרוץ את הקוד של מי שמשתמש בחבילה שלך. דרך אחת למזער שינויים שמפריעים היא לאפשר גישה רק לחלקים מהחבילה שנחוצים לשימוש הנכון שלה. על ידי הגבלת הגישה, אתה יכול לבצע שינויים פנימיים בחבילה שלך עם פחות סיכוי להשפיע על אופן השימוש של מפתחים אחרים בחבילה שלך.
במאמר זה, תלמד כיצד לשלוט ברמת הגישה לחבילות, כמו גם כיצד להגן על חלקים מהקוד שצריכים לשמש רק בתוך החבילה שלך. כדי לעשות זאת, ניצור יומן בסיסי ליומן וניפוי שגיאות, תוך שימוש בחבילות עם רמות גישה שונות לפריטים.
דרישות מוקדמות
כדי לעקוב אחרי הדוגמאות במאמר זה, תצטרך:
- להגדיר סביבת עבודה של Go על ידי ביצוע כיצד להתקין Go ולהגדיר סביבת תכנות מקומית. מדריך זה ישתמש במבנה הקבצים הבא:
.
├── bin
│
└── src
└── github.com
└── gopherguides
פריטים מיוצאים ולא מיוצאים
בניגוד לשפות תכנות אחרות כמו Java ו-Python שמשתמשות במגבלות גישה כמו public
, private
, או protected
לציון היקף, Go קובעת אם פריט הוא מיוצא
ו-לא מיוצא
דרך הצהרתו. ייצוא פריט במקרה זה הופך אותו לנראה
מחוץ לחבילה הנוכחית. אם הוא לא מיוצא, הוא נראה וניתן לשימוש רק מתוך החבילה שבה הוגדר.
הנראות החיצונית נשלטת על ידי האות הראשונה הגדולה של הפריט שהוכרז. כל הצהרות, כמו סוגים
, משתנים
, קבועים
, פונקציות
וכו', שמתחילות באות גדולה נראות מחוץ לחבילה הנוכחית.
בואו נבחן את הקוד הבא, תוך שימוש קשב לאותיות הגדולות:
package greet
import "fmt"
var Greeting string
func Hello(name string) string {
return fmt.Sprintf(Greeting, name)
}
קוד זה קובע שהוא נמצא בחבילת greet
. לאחר מכן, הוא קובע שני סמלים, משתנה בשם Greeting
ופונקציה בשם Hello
. מכיוון ששניהם מתחילים באות גדולה, הם שניהם יוצאים
וזמינים לכל תוכנית חיצונית. כפי שצוין קודם, יצירת חבילה שמגבילה גישה תאפשר עיצוב API טוב יותר ותקל על עדכון החבילה שלך באופן פנימי מבלי לשבור קוד של אחרים שתלוי בחבילה שלך.
הגדרת נראות חבילה
כדי להביט יותר מקרוב כיצד נראות החבילה עובדת בתוכנית, בואו ניצור חבילת logging
, תוך התחשבות במה שאנו רוצים להפוך לנראה מחוץ לחבילה שלנו ובמה שלא נפיץ. חבילת היומונג זו תהיה אחראית ליומונג כל הודעות התוכנית שלנו לקונסולה. היא גם תבדוק באיזה רמה אנו מתעדים. רמה מתארת את סוג היומן, והיא תהיה אחת משלוש מצבים: info
, warning
, או error
.
ראשית, בתוך תיקיית src
שלך, בואו ניצור תיקייה בשם logging
כדי להכניס את קבצי היומונג שלנו לתוכה:
זז לתוך התיקייה הזו הבאה:
לאחר מכן, באמצעות עורך כמו nano, צור קובץ בשם logging.go
:
שים את הקוד הבא בקובץ logging.go
שיצרנו זה עתה:
השורה הראשונה של הקוד הזה הכריזה על חבילה בשם logging
. בחבילה זו, יש שתי פונקציות מיוצאות
: Debug
ו-Log
. ניתן לקרוא לפונקציות אלו על ידי כל חבילה אחרת שייבאה את החבילה logging
. יש גם משתנה פרטי בשם debug
. משתנה זה נגיש רק מתוך החבילה logging
. חשוב לציין שבעוד שהפונקציה Debug
והמשתנה debug
שניהם בעלי אותה הכתיבה, הפונקציה היא באות גדולה והמשתנה אינו. זה הופך אותם להכרזות נפרדות עם תחומים שונים.
שמור וצא מהקובץ.
כדי להשתמש בחבילה זו בשטחים אחרים של הקוד שלנו, נוכל לייבא
אותה לתוך חבילה חדשה. ניצור חבילה חדשה זו, אך נצטרך תחילה ספרייה חדשה לאחסון קבצי המקור האלה בה.
בואו נזיז את עצמנו מחוץ לספרייה logging
, ניצור ספרייה חדשה בשם cmd
, ונעבור לספרייה החדשה זו:
צור קובץ בשם main.go
בספריית cmd
שיצרנו זה עתה:
עכשיו נוכל להוסיף את הקוד הבא:
כעת יש לנו את כל התוכנית שלנו כתובה. עם זאת, לפני שנוכל להריץ את התוכנית הזו, נצטרך גם ליצור כמה קבצי תצורה כדי שהקוד שלנו יעבוד כראוי. Go משתמשת ב-Go Modules כדי להגדיר תלויות חבילות לייבוא משאבים. מודולי Go הם קבצי תצורה שממוקמים במדריך החבילה שלך שמודיעים למהדר מאיפה לייבא חבילות. למרות שלמידה על מודולים חורגת מתחום מאמר זה, אנו יכולים לכתוב רק כמה שורות של תצורה כדי לגרום לדוגמה זו לעבוד באופן מקומי.
פתח את הקובץ go.mod
במדריך cmd
:
והכנס את התוכן הבא לקובץ:
module github.com/gopherguides/cmd
replace github.com/gopherguides/logging => ../logging
השורה הראשונה בקובץ זה אומרת למהדר של החבילה cmd
יש נתיב קובץ של github.com/gopherguides/cmd
. השורה השנייה אומרת למהדר שהחבילה github.com/gopherguides/logging
ניתן למצוא בדיסק מקומי במדריך ../logging
.
נצטרך גם קובץ go.mod
עבור החבילה שלנו logging
. בואו נחזור למדריך logging
וניצור קובץ go.mod
:
הוסף את התוכן הבא לקובץ:
module github.com/gopherguides/logging
זה אומר למהדר שהחבילה logging
שיצרנו היא למעשה החבילה github.com/gopherguides/logging
. זה מאפשר לייבא את החבילה בחבילה שלנו main
עם השורה הבאה שכתבנו קודם:
package main
import "github.com/gopherguides/logging"
func main() {
logging.Debug(true)
logging.Log("This is a debug statement...")
}
כעת עליך להיות עם מבנה המדריכים ופריסת הקבצים הבאים:
├── cmd
│ ├── go.mod
│ └── main.go
└── logging
├── go.mod
└── logging.go
עכשיו שהשלמנו את כל התצורה, אנו יכולים להריץ את התכנית main
מהחבילה cmd
עם הפקודות הבאות:
תקבל פלט דומה למה שבא להלן:
Output2019-08-28T11:36:09-05:00 This is a debug statement...
התכנית תדפיס את השעה הנוכחית בפורמט RFC 3339, ולאחר מכן את ההצהרה ששלחנו ליומן. RFC 3339 הוא פורמט זמן שתוכנן לייצוג זמן באינטרנט ומשמש בדרך כלל בקבצי יומן.
מכיוון שהפונקציות Debug
ו-Log
מיוצאות מהחבילה ליומן, אנו יכולים להשתמש בהן בחבילת main
. עם זאת, המשתנה debug
בחבילת logging
אינו מיוצא. ניסיון להתייחס להצהרה לא מיוצאת יגרום לשגיאת קומפילציה.
הוסף את השורה המודגשת הבאה ל-main.go
:
package main
import "github.com/gopherguides/logging"
func main() {
logging.Debug(true)
logging.Log("This is a debug statement...")
fmt.Println(logging.debug)
}
שמור והרץ את הקובץ. תקבל שגיאה דומה למה שבא להלן:
Output. . .
./main.go:10:14: cannot refer to unexported name logging.debug
עכשיו שראינו כיצד פריטים מיוצאים
ו-לא מיוצאים
בחבילות מתנהגים, נבחן בהמשך כיצד שדות
ו-שיטות
יכולים להיות מיוצאים מ-מבנים
.
נראות בתוך מבנים
בעוד שתבנית הנראות בlogger שבנינו בסעיף הקודם עשויה לעבוד עבור תוכניות פשוטות, היא חולקת מדי הרבה מצב כדי להיות שימושית מתוך מספר חבילות. זה בגלל שהמשתנים המיוצאים נגישים למספר חבילות שעשויות לשנות את המשתנים למצבים סותרים. מתן האפשרות למצב של החבילה שלך להשתנות בדרך זו מקשה על צפייה באופן פעולת התוכנית שלך. עם העיצוב הנוכחי, לדוגמה, חבילה אחת יכולה להגדיר את המשתנה Debug
ל-true
, וחבילה אחרת יכולה להגדיר אותו ל-false
באותה מופע. זה ייצור בעיה מאחר ששתי החבילות שמייבאות את החבילה logging
מושפעות.
אנו יכולים לבודד את הlogger על ידי יצירת struct ואז לתלות שיטות עליו. זה יאפשר לנו ליצור מופע
של logger לשימוש באופן עצמאי בכל חבילה שמשתמשת בו.
שנה את החבילה logging
למצב הבא כדי לעבור על הקוד ולבודד את הlogger:
בקוד זה, יצרנו מבנה Logger
. מבנה זה יארח את המצב הלא מיובא שלנו, כולל פורמט הזמן להדפסה והמשתנה debug
שמוגדר כ-true
או false
. הפונקציה New
מגדירה את המצב הראשוני ליצירת הלוגר, כגון פורמט הזמן ומצב הדיבאג. לאחר מכן היא מאחסנת את הערכים שהעברנו לה בתוך המשתנים הלא מיובאים timeFormat
ו-debug
. יצרנו גם שיטה בשם Log
על סוג Logger
שמקבלת משפט שאנו רוצים להדפיס. בתוך השיטה Log
יש הפניה למשתנה המקומי של השיטה l
כדי לקבל גישה חזרה לשדות הפנימיים שלה כמו l.timeFormat
ו-l.debug
.
גישה זו תאפשר לנו ליצור Logger
בחבילות רבות שונות ולהשתמש בו באופן עצמאי מכיצד החבילות האחרות משתמשות בו.
כדי להשתמש בו בחבילה אחרת, בואו נשנה את cmd/main.go
להיראות כמו הבא:
הרצת התוכנית תיתן לך את הפלט הבא:
Output2019-08-28T11:56:49-05:00 This is a debug statement...
בקוד זה, יצרנו מופע של הלוגר על ידי קריאה לפונקציה המיובאת New
. אחסנו את ההפניה למופע זה במשתנה logger
. כעת ניתן לקרוא ל-logging.Log
כדי להדפיס משפטים.
אם ננסה להפנות לשדה לא מיובא מה-Logger
כמו השדה timeFormat
, נקבל שגיאת קומפילציה. נסה להוסיף את השורה המודגשת הבאה ולהריץ את cmd/main.go
:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, true)
logger.Log("This is a debug statement...")
fmt.Println(logger.timeFormat)
}
זה ייתן את השגיאה הבאה:
Output. . .
cmd/main.go:14:20: logger.timeFormat undefined (cannot refer to unexported field or method timeFormat)
המהדר מבחין ש-logger.timeFormat
אינו מיוצא, ולכן לא ניתן לאחזר אותו מהחבילה logging
.
נראות בתוך שיטות
באותו אופן כמו שדות מבנה, גם שיטות יכולות להיות מיוצאות או לא מיוצאות.
כדי להמחיש זאת, בואו נוסיף יומן דרגתי leveled ליומן שלנו. יומן דרגתי הוא דרך לסווג את היומנים שלך כך שתוכל לחפש ביומנים שלך עבור סוגים ספציפיים של אירועים. הרמות שנכניס ליומן שלנו הן:
-
רמת
info
, המייצגת אירועים מסוג מידע שמודיעים למשתמש על פעולה, כגוןהתוכנית התחילה
, אוהודעת דואר אלקטרוני נשלחה
. אלו עוזרות לנו לנפות באגים ולעקוב אחר חלקים מהתוכנית שלנו כדי לראות אם ההתנהגות הצפויה מתרחשת. -
רמת
warning
. אירועים אלו מזהים מתי משהו לא צפוי קורה שאינו שגיאה, כמוהודעת דואר אלקטרוני נכשלה לשלוח, ניסיון חוזר
. הם עוזרים לנו לראות חלקים מהתוכנית שלנו שאינם מתנהלים בצורה חלקה כפי שציפינו. -
הרמה `
error
`, מה שאומר שהתכנית נתקלה בבעיה, כמו `File not found
`. זה לעתים קרובות יגרום לכישלון בפעולת התכנית.
ייתכן שתרצה גם להפעיל ולכבות רמות מסוימות של רישום, במיוחד אם התכנית שלך לא מבצעת כפי שצפוי ותרצה לנפות את התכנית. נוסיף פונקציונליות זו על ידי שינוי התכנית כך שכאשר `debug
` מוגדר ל-`true
`, הוא ידפיס את כל רמות הודעות. אחרת, אם זה `false
`, הוא ידפיס רק הודעות שגיאה.
הוסף רישום מתואם על ידי ביצוע השינויים הבאים ל-`logging/logging.go
`:
בדוגמה זו, הצגנו טיעון חדש למתודה `Log
`. כעת ניתן להעביר את ה-`level
` של הודעת היומן. המתודה `Log
` קובעת איזו רמת הודעה זה. אם זו הודעת `info
` או `warning
`, והשדה `debug
` הוא `true
`, אז היא כותבת את ההודעה. אחרת היא מתעלמת מההודעה. אם זו כל רמה אחרת, כמו `error
`, היא תכתוב את ההודעה ללא קשר.
רוב ההגיון לקביעה אם הודעה תודפס קיים במתודה Log
. הוספנו גם מתודה לא מיוצאת בשם write
. המתודה write
היא מה שבאמת מוציא את הודעת היומן.
נוכל כעת להשתמש ביומן מפורט זה בחבילה האחרת שלנו על ידי שינוי cmd/main.go
להיראות כמו הבא:
הרצת זה תיתן לך:
Output[info] 2019-09-23T20:53:38Z starting up service
[warning] 2019-09-23T20:53:38Z no tasks found
[error] 2019-09-23T20:53:38Z exiting: no work performed
בדוגמה זו, cmd/main.go
השתמש בהצלחה במתודה Log
המיוצאת.
נוכל כעת להעביר את level
של כל הודעה על ידי החלפת debug
ל-false
:
עכשיו נראה שרק הודעות ברמת error
מודפסות:
Output[error] 2019-08-28T13:58:52-05:00 exiting: no work performed
אם ננסה לקרוא למתודה write
מחוץ לחבילה logging
, נקבל שגיאת קומפילציה:
package main
import (
"time"
"github.com/gopherguides/logging"
)
func main() {
logger := logging.New(time.RFC3339, true)
logger.Log("info", "starting up service")
logger.Log("warning", "no tasks found")
logger.Log("error", "exiting: no work performed")
logger.write("error", "log this message...")
}
Outputcmd/main.go:16:8: logger.write undefined (cannot refer to unexported field or method logging.(*Logger).write)
כשהקומפיילר רואה שאתה מנסה להתייחס למשהו מחבילה אחרת שמתחיל באות קטנה, הוא יודע שזה לא מיוצא, ולכן זורק שגיאת קומפיילציה.
הלוגר במדריך זה ממחיש כיצד אנו יכולים לכתוב קוד שמגלה רק את החלקים שאנו רוצים שחבילות אחרות יצרכו. מכיוון שאנו שולטים בחלקים מהחבילה שגלויים מחוץ לחבילה, אנו יכולים כעת לבצע שינויים עתידיים מבלי להשפיע על כל קוד שתלוי בחבילה שלנו. לדוגמה, אם היינו רוצים לכבות רק הודעות ברמת info
כאשר debug
הוא שקר, תוכל לבצע שינוי זה מבלי להשפיע על כל חלק אחר של ה-API שלך. יכולנו גם לבצע שינויים בטוחים בהודעת הלוג כדי לכלול מידע נוסף, כמו התיקייה שממנה התוכנית רצה.
מסקנה
מאמר זה הראה כיצד לשתף קוד בין חבילות תוך גם מיגון פרטי היישום של החבילה שלך. זה מאפשר לך לייצא API פשוט שישתנה לעתים רחוקות מסיבות תאימות לאחור, אך יאפשר שינויים פרטיים בחבילה שלך כפי שנדרש כדי להפוך אותה לעבוד טוב יותר בעתיד. זה נחשב לטובה העיקרית בעת יצירת חבילות וה-APIs המקבילים להן.
כדי ללמוד עוד על חבילות ב-Go, עיין במאמרינו ייבוא חבילות ב-Go ו-כיצד לכתוב חבילות ב-Go, או חקור את כל הסדרה שלנו כיצד לתכנת ב-Go.
Source:
https://www.digitalocean.com/community/tutorials/understanding-package-visibility-in-go