راهنمای اصول و قواعد کدنویسی به زبان سوئیفت.
هدف از این راهنما، تشویق به استفاده از الگوهای نوشتاری است تا اهداف زیر (به ترتیب اهمیت تقریبی) برآورده شود:
- افزایش دقت و کاهش احتمال خطاهای برنامه نویسی
- افزایش شفافیت در نیت
- کاهش میزان کدنویسی (پرگویی در کد)
- مباحثه کمتر در مورد زیباییشناسی کد
اگر پیشنهادی دارید، لطفاً راهنمای کمک به ما را مطالعه کنید.:zap:
- به جای Space از Tab استفاده کنید.
- فایلها را با یک خط خالی در انتها ببندید.
- آزادانه از فاصلهی عمودی (خطهای خالی) استفاده کنید تا کد خود را به تکههای منطقی تقسیم کنید.
- فاصلههای حاشیهای بی مورد اضافه نکنید.
- حتی خطوط جدید را با عقببردگی شروع نکنید.
هر جا ممکن بود (و اگر شک داشتید) به جای `var foo = ...` از `let foo = ...` استفاده کنید. فقط وقتی از `var` استفاده کنید که مطلقاً مجبور باشید. (یعنی وقتی که میدانید مقدار متغیر ممکن است تغییر کند. برای مثال وقتی که متغیر شما از نوع `weak` باشد).
استدلال: هدف و مفهوم هر دو عبارت واضح است، اما استفادهی پیش فرض از `let` موجب اطمینان و شفافیت بیشتر کد میشود.
استفاده از `let` نه تنها تضمین میکند که مقدار تغییر نمیکند، این مفهوم را به روشنی به برنامهنویس منتقل میکند. در نتیجه کدها پس از آن میتوانند با اطمینان از این عدم تغییر نوشته شوند.
بدین ترتیب توجیه کدها سادهتر میشود. اگر شما از `var` استفاده کنید و همچنان فرض کنید که مقدار تغییر نمیکند، مجبور خواهید بود خودتان آنرا بیازمایید.
به همین ترتیب، هر جایی یک `var` مشاهده کردید، میتوانید فرض کنید که مقدار آن تغییر میکند و از خودتان بپرسید چرا؟
اگر شرایط خاصی برای ادامه عملکرد کد خود دارید، سعی کنید در صورت امکان سریعتر از آن خارج شوید. در نتیجه، به جای:
```swift if n.isNumber { // Use n here } else { return } ```از این کد استفاده کنید:
guard n.isNumber else {
return
}
// Use n here
شما میتوانید از `if` هم استفاده کنید، اما استفاده از `guard` ارجح است، زیرا `guard` بدون `return`، `break` و یا `continue` خطای زمان کامپایل ایجاد میکند، در نتیجه خروج بموقع تضمین میشود.
اگر یک متغیر به نام `foo` و از نوع `FooType?` یا `FooType!` دارید، در صورت امکان آنرا به اجبار بازگشایی نکنید تا به مقدار آن دسترسی پیدا کنید (`foo!`).
این روش ارجح است:
if let foo = foo {
// Use unwrapped `foo` value in here
} else {
// If appropriate, handle the case where the optional is nil
}
همچنین ممکن است در بعضی از این حالات بخواهید از قابلیت تداوم زنجیرهی اختیاری در سوئیفت استفاده کنید.
// Call the function if `foo` is not nil. If `foo` is nil, ignore we ever tried to make the call
foo?.callSomethingIfFooIsNotNil()
استدلال: استفادهی غیر ضمنی از `if let` برای متغیرهای انتخابی منتج به کدی مطمئنتر میشود. گشایش اجباری موجبات خطاهای زمان اجرا را فراهم میکند.
اگر `foo` ممکن است nil باشد، در صورت امکان از `let foo: FooType?` به جای `let foo: FooType!` استفاده کنید. (توجه داشته باشید که عموماً میتوان به جای `!` از `?` استفاده کرد.)
استدلال: متغیرهای اختیاری غیر ضمنی، منتج به کدهای مطمئنتر میشوند. متغیرهای اختیاری ظاهراً اجباری امکان خطاهای زمان اجرا را فراهم میکنند.
هرگاه امکانپذیر بود، عبارت `get` را از متغیرهای فقط خواندنی ویا زیرمحموعهخانیهای فقط خواندنی حذف کنید.
در نتیجه بنویسید:
var myGreatProperty: Int {
return 4
}
subscript(index: Int) -> T {
return objects[index]
}
و ننویسید:
var myGreatProperty: Int {
get {
return 4
}
}
subscript(index: Int) -> T {
get {
return objects[index]
}
}
استدلال:هدف و مفهوم مدل اول مشخص است و در نتیجه کد کمتری خواهیم نوشت.
توابع سطح بالا، تعریف نوع و یا متغیرها باید همیشه تعریف مشخص سطح دسترسی داشته باشند:
public var whoopsGlobalState: Int
internal struct TheFez {}
private func doTheThings(things: [Thing]) {}
ولیکن تعاریف درونی آنها، هرگاه مناسب بود، می توانند سطح دسترسی ضمنی داشته باشند:
internal struct TheFez {
var owner: Person = Joshaber()
}
استدلال: به ندرت مناسب است که تعاریف سطح بالا `internal` باشند، بیان صریح آن تضمین میکند که به این موضوع قبل از تصمیمگیری فکر کنیم. در داخل آن تعاریف، استفاده مجدد همان سطح دسترسی تکرار مکررات است و مقادیر پیشفرض معمولاً مناسب هستند.
هنگامی که نوع یک مشخصه را به آن اتلاق می کنید، همواره دونقطه را به اسم آن بپجسانید، سپس یک فاصله بگذارید و سپس نوع آن را بنویسید.
class SmallBatchSustainableFairtrade: Coffee { ... }
let timeToCoffee: NSTimeInterval = 2
func makeCoffee(type: CoffeeType) -> Coffee { ... }
استدلال: موئلفهی نوع، مطلبی در بارهی آن مشخصه میگوید، درنتیجه باید کنار آن قرار گیرد.
همچنین، وقتی یک نوع را در یک دیکشنری مشخص میکنید، همواره دونقطه را بلافاصله پس از کلید بنویسید، در ادامه یک فاصله بگذارید و سپس نوع مقدار را بنویسید.
let capitals: [Country: City] = [ Sweden: Stockholm ]
هر گار به متغیرها و یا توابع `self` نیاز دارید، اشاره به `self` را به قرینهی ضمنی حذف کنید.
private class History {
var events: [Event]
func rewrite() {
events = []
}
}
فقط به وقتی که بالاجبار مجبور هستید، به طور مثال در یک closure و یا در صورت تداخل نامها عبارت مورد نظر را استفاده کنید:
extension History {
init(events: [Event]) {
self.events = events
}
var whenVictorious: () -> () {
return {
self.rewrite()
}
}
}
استدلال: بدینتریب بعد معنایی تسخیر `self` بارزتر میشود و از زیادهنویسی در جاهای دیگر خودداری میشود.
به جز در مواردی که نیازمندی دارید که تنها با کلاس قابل پیاده سازی است (مانند هویت شی و یا deinitializers) شی مورد نظر را با استفاده از struct ها پیاده سازی کنید.
توجه داشته باشید که وراثت به نوبه خود و به تنهایی، دلیل خوبی برای استفاده از class نیست، زیرا چند وجهی بودن را می توان با استفاده از پروتکل ها و استفادهی مجدد از کدها را از طریق ترکیب میتوان پیادهسازی کرد.
برای مثال، وراثت در این class:
class Vehicle {
let numberOfWheels: Int
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
func maximumTotalTirePressure(pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(numberOfWheels)
}
}
class Bicycle: Vehicle {
init() {
super.init(numberOfWheels: 2)
}
}
class Car: Vehicle {
init() {
super.init(numberOfWheels: 4)
}
}
میتواند با این تعاریف خلاصهسازی شود:
protocol Vehicle {
var numberOfWheels: Int { get }
}
func maximumTotalTirePressure(vehicle: Vehicle, pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(vehicle.numberOfWheels)
}
struct Bicycle: Vehicle {
let numberOfWheels = 2
}
struct Car: Vehicle {
let numberOfWheels = 4
}
استدلال: متغیرهای مقداری با استفاده از `let` رفتار قابل پیشبینیتری دارند.
کلاسها باید به شکل `final` شروع شوند و فقط وقتی باید اجازه Subclassing داده شود که یک نیاز واقعی و صحیح برای وراثت تشخیص داده شده باشد. حتی در اینصورت نیز تا آنجایی که ممکن است، تعاریف داخلی آن کلاس باید `final` باشند و به نوبه خود از این قاعده پیروی کنند.
استدلال: ترکیب عموماً به وراثت ارجحیت دارد و انتخاب وراثت بدین معنی خواهد بود که در مورد آن به اندازه کافی فکر شده است.
متدهای کلاسهایی که نوع در آنها بصورت پارامتر تعریف شده، می توانند از ذکر آن صرفنظر کنند (در شرایطی که نوع مطرح شده با آنجا در کلاس تعریف شده یکی باشد.) برای مثال:
struct Composite<T> {
…
func compose(other: Composite<T>) -> Composite<T> {
return Composite<T>(self, other)
}
}
میتوانست به این شکل باشد:
struct Composite<T> {
…
func compose(other: Composite) -> Composite {
return Composite(self, other)
}
}
استدلال: صرفنظر کردن از پارامترهای نوعِ اضافی میتواند منطق وجود آنها را روشنتر کرده و همچنین شفافیت بیشتری به هنگامی که مقدار بازگشتی نوع متفاوتی دارد ایجاد می کند.
هنگام تعریف عملگرها در اطراف آن فضای خالی در نظر بگیرید. به جای:
func <|(lhs: Int, rhs: Int) -> Int
func <|<<A>(lhs: A, rhs: A) -> A
بنویسید:
func <| (lhs: Int, rhs: Int) -> Int
func <|< <A>(lhs: A, rhs: A) -> A
استدلال: عملگرهای شامل حروف نقطهگذاری میشوند که در صورت قرار گرفتن در کنار نقطهگذاری در اطراف آن، موجب سختی در خواندن کد میشوند. اضافه کردن فاصله در اطراف عملگر موجب خوانایی بیشتر کد میشود.
ترجمهی این متن به دلایل زیر انجام شد:
- وجود یک راهنمای خوب به زبان فارسی برای کد نویسان فارسی زبان
- نشان دادن این موضوع که ترجمهی متون فنی و بویژه در حوزهی برنامه نویسی، کار بسیار بیهودهای است، چونکه بیشتر کلمات برگردان غلط و گمراهکننده ای هستند که غالباً نمیتوانند مفهوم را به خوبی منتقل کنند. لذا پیشنهاد آن است که تا آنجا که می توانید در جهت فراگیری زبان انگلیسی تخصصی برای حرفهی خود تلاش کنید!