אנו נתקלים באופן קבוע בפרויקטים שבהם, חודש לפני השחרור, מתברר שמפתח API של סביבת debug נכנס לסביבת הייצור. זה לא סיכון היפותטי — בניסיון שלנו, היו כ-30 פרויקטים כאלה בשנים האחרונות. תצורת build נכונה עבור סביבות Debug, Release ו-Staging פירושה שלכל סביבה יש קבועים, התנהגות והגדרות משלה, ולא קבוצה של #if DEBUG הפזורים ברחבי הקוד. לדוגמה, אחד הלקוחות שלנו הפסיד $4.5k–6.5k מכיוון שהודעות push לבדיקה נשלחו למשתמשים אמיתיים. ניתן היה למנוע דליפה זו על ידי הפרדת תצורות פשוטה. אנו מגדירים builds כבר למעלה מ-5 שנים ויישמנו יותר מ-40 פרויקטים — זה מאפשר לנו להבטיח שחרור מהיר ובטוח.
למה להפריד תצורות Build?
מצב קלאסי: אפליקציה עוברת לייצור עם כתובת URL בסיסית מקודדת של https://api-dev.myapp.com. או שבודקים מקבלים build שמתעד הכל לקונסולה וקורס בגלל StrictMode מופעל. תצורת Staging מפחיתה את הסיכון לקריסות כאלה פי 3 בהשוואה ל-Debug, ו-Release מבטלת אותן לחלוטין. לאחר הגדרת ה-builds, הבעיות הללו נעלמות לנצח. הפרדת תצורות build מפחיתה את זמן הניפוי פי 2 בהשוואה לפרויקט מונוליטי.
איך זה עובד ב-iOS
ב-Xcode, ישנן שתי תצורות build ברירת מחדל: Debug ו-Release. Staging מתווספת ידנית: Product → Scheme → Edit Scheme → Duplicate Release → שנה שם ל-Staging.
לאחסון ערכי תצורה, אנו משתמשים בקבצי .xcconfig:
// Config/Debug.xcconfig
API_BASE_URL = https://api-dev.myapp.com
LOG_LEVEL = verbose
BUNDLE_ID_SUFFIX = .debug
// Config/Staging.xcconfig
API_BASE_URL = https://api-staging.myapp.com
LOG_LEVEL = info
BUNDLE_ID_SUFFIX = .staging
// Config/Release.xcconfig
API_BASE_URL = https://api.myapp.com
LOG_LEVEL = error
BUNDLE_ID_SUFFIX = ב-Info.plist, ערכים נמשכים דרך // Config/Debug.xcconfig API_BASE_URL = https://api-dev.myapp.com LOG_LEVEL = verbose BUNDLE_ID_SUFFIX = .debug // Config/Staging.xcconfig API_BASE_URL = https://api-staging.myapp.com LOG_LEVEL = info BUNDLE_ID_SUFFIX = .staging // Config/Release.xcconfig API_BASE_URL = https://api.myapp.com LOG_LEVEL = error BUNDLE_ID_SUFFIX = . בקוד, הם נקראים דרך $(API_BASE_URL):
enum AppConfig {
static var apiBaseURL: URL {
guard let urlString = Bundle.main.object(forInfoDictionaryKey: "API_BASE_URL") as? String,
let url = URL(string: urlString) else {
fatalError("API_BASE_URL not configured")
}
return url
}
}אין Bundle.main.infoDictionary עבור כתובות URL — רק Bundle.
באמצעות אותו scheme, אנו מגדירים אייקונים ושמות אפליקציה נפרדים: הוסף נכסי enum AppConfig { static var apiBaseURL: URL { guard let urlString = Bundle.main.object(forInfoDictionaryKey: "API_BASE_URL") as? String, let url = URL(string: urlString) else { fatalError("API_BASE_URL not configured") } return url } } שונים ותנאי ב-#if DEBUG (AppIcon).
מה עם אנדרואיד?
באנדרואיד, תצורות מנוהלות דרך xcconfig. Build Types (debug, release, staging) + Product Flavors נותנים מטריצה של וריאנטים.
android {
buildTypes {
debug {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
isDebuggable = true
buildConfigField("String", "API_BASE_URL", "\"https://api-dev.myapp.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
}
create("staging") {
initWith(getByName("release"))
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
buildConfigField("String", "API_BASE_URL", "\"https://api-staging.myapp.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
signingConfig = signingConfigs.getByName("debug")
}
release {
isMinifyEnabled = true
buildConfigField("String", "API_BASE_URL", "\"https://api.myapp.com\"")
buildConfigField("Boolean", "ENABLE_LOGGING", "false")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
}ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon-Staging זמין בקוד לאחר בנייה — זהו מחלקה שנוצרת. שים לב: ב-Release אנו מפעילים ProGuard/R8, שמקטין את גודל ה-APK ב-30-40%. אייקונים ושמות נפרדים עבור Staging ו-Debug מוגדרים דרך build.gradle.kts ו-android { buildTypes { debug { applicationIdSuffix = ".debug" versionNameSuffix = "-debug" isDebuggable = true buildConfigField("String", "API_BASE_URL", "\"https://api-dev.myapp.com\"") buildConfigField("Boolean", "ENABLE_LOGGING", "true") } create("staging") { initWith(getByName("release")) applicationIdSuffix = ".staging" versionNameSuffix = "-staging" buildConfigField("String", "API_BASE_URL", "\"https://api-staging.myapp.com\"") buildConfigField("Boolean", "ENABLE_LOGGING", "true") signingConfig = signingConfigs.getByName("debug") } release { isMinifyEnabled = true buildConfigField("String", "API_BASE_URL", "\"https://api.myapp.com\"") buildConfigField("Boolean", "ENABLE_LOGGING", "false") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } } .
מה עם פלטפורמות חוצות?
ב-React Native, תצורות סביבה מנוהלות דרך BuildConfig.API_BASE_URL או סוגי build מקוריים. החבילה מהדרת משתנים לקוד מקורי — ערכים אינם זמינים דרך src/debug/res/ במהלך זמן הריצה של JS.
ב-Flutter — src/staging/res/ או react-native-config:
flutter build apk --dart-define=API_URL=https://api-staging.myapp.com --flavor staging השוואת גישות
| פלטפורמה | שיטת אחסון | יתרונות | הערות |
|---|---|---|---|
| iOS (Swift) | process.env + Info.plist | אינטגרציה נקייה עם Xcode | דורש בנייה מחדש של scheme עבור סביבה חדשה |
| אנדרואיד (Kotlin) | --dart-define / --dart-define-from-file | יצירה אוטומטית | BuildConfig נשמר דרך ProGuard |
| Flutter | flutter build apk --dart-define=API_URL=https://api-staging.myapp.com --flavor staging / .xcconfig | פשטות | יש להעביר במפורש במהלך הבנייה |
| React Native | buildConfigField | בידוד | תלות נוספת, התעלם מקבצי BuildConfig |
מה כוללת העבודה ואיך אנחנו עושים אותה
אנחנו לא רק כותבים קבצי תצורה — אנו מנתחים את הפרויקט, מוצאים את כל המקומות עם פרקטיקות מקודדות ולא בטוחות, יוצרים מטריצת תצורה, בוחרים את הגישה האופטימלית (Xcconfig, buildConfigField, --dart-define), מיישמים, בודקים כל תצורה, ומגדירים פרסום אוטומטי. בסופו של דבר, אתה מקבל:
- ביקורת על מצב התצורה הנוכחי.
- יצירת xcconfig/buildTypes/productFlavors עבור כל הסביבות.
- העברת ערכים מקודדים לקבצי תצורה.
- הגדרת אייקונים ושמות אפליקציה עבור Staging/Debug.
- עדכון סקריפטים של CI.
- תיעוד לצוות.
- תמיכה למשך שבועיים לאחר המסירה.
כמה זמן לוקחת הגדרת התצורה?
| סוג פרויקט | זמן | תלות |
|---|---|---|
| פלטפורמה אחת (iOS או אנדרואיד) | 1–2 ימים | גישה לקוד המקור ולמערכת CI |
| שתי הפלטפורמות | 2–3 ימים | גישה לחשבונות App Store ו-Google Play |
| +Flutter או React Native | +1 יום | קבצי Flutter define או .env |
העלות מחושבת באופן אישי. צור קשר כדי לקבל ייעוץ והערכה מדויקת לפרויקט שלך.
אילו טעויות מונעת תצורה נכונה?
תצורה נכונה מונעת דליפה של נתונים סודיים (מפתחות ייצור לא מגיעים ל-builds של debug), בלבול בין סביבות (בודקים תמיד רואים איזו גרסה מותקנת), וכשלים עקב התנהגות שונה (ב-Staging ניתן להפעיל לוגינג ללא סיכון לקריסות). לאחר יישום פרקטיקות אלה, תשכח מבעיות הקשורות לתצורות build. הזמן הגדרת תצורה היום — והפחת את הסיכון להפסדים כספיים. קבל ייעוץ עכשיו.
תיעוד ProGuard: https://www.guardsquare.com/manual







