@sun-asterisk/sunlint 1.3.40 → 1.3.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/rule-selection-service.js +11 -0
- package/package.json +1 -1
- package/skill-assets/sunlint-code-quality/rules/dart/D001-recommended-lints.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D002-dispose-resources.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D003-prefer-widget-classes.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D004-avoid-shrinkwrap.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D005-widget-nesting.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D006-large-callbacks.md +54 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D007-lifecycle-order.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D008-long-functions.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D009-function-parameters.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D010-cyclomatic-complexity.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D011-named-parameters.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D012-named-booleans.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D013-single-public-class.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D014-safe-collection-access.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D015-copywith-consistency.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D016-project-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D017-review-dependencies.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D018-no-commented-code.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D019-single-child-wrappers.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D020-if-else-limit.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D021-negated-booleans.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D022-setstate-usage.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D023-unnecessary-overrides.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D024-avoid-unnecessary-statefulwidget.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/dart/D025-nested-ternaries.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/go/C006-verb-noun-functions.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C013-no-dead-code.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C014-dependency-injection.md +85 -0
- package/skill-assets/sunlint-code-quality/rules/go/C017-no-constructor-logic.md +67 -0
- package/skill-assets/sunlint-code-quality/rules/go/C018-generic-errors.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/C019-error-log-level.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/C020-no-unused-imports.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C022-no-unused-variables.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/C023-no-duplicate-names.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/C024-centralize-constants.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/C029-catch-log-root-cause.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/C030-custom-error-classes.md +69 -0
- package/skill-assets/sunlint-code-quality/rules/go/C033-separate-data-access.md +68 -0
- package/skill-assets/sunlint-code-quality/rules/go/C035-error-context-logging.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/C041-no-hardcoded-secrets.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/go/C042-boolean-naming.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/C052-controller-parsing.md +62 -0
- package/skill-assets/sunlint-code-quality/rules/go/C060-superclass-logic.md +60 -0
- package/skill-assets/sunlint-code-quality/rules/go/C067-no-hardcoded-config.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S003-open-redirect.md +80 -0
- package/skill-assets/sunlint-code-quality/rules/go/S004-no-log-credentials.md +66 -0
- package/skill-assets/sunlint-code-quality/rules/go/S005-server-authorization.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S006-default-credentials.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S007-output-encoding.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S009-approved-crypto.md +63 -0
- package/skill-assets/sunlint-code-quality/rules/go/S010-csprng.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S011-encrypted-client-hello.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/go/S012-secrets-management.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S013-tls-connections.md +61 -0
- package/skill-assets/sunlint-code-quality/rules/go/S016-no-sensitive-query-string.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S017-parameterized-queries.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/go/S019-email-input-sanitization.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S020-eval-code-execution.md +47 -0
- package/skill-assets/sunlint-code-quality/rules/go/S022-context-escaping.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S023-dynamic-js-encoding.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S025-server-validation.md +57 -0
- package/skill-assets/sunlint-code-quality/rules/go/S026-tls-encryption.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S027-mtls-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S028-upload-limits.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S029-csrf-protection.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S030-directory-browsing.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S031-secure-cookie-flag.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S032-httponly-cookie.md +42 -0
- package/skill-assets/sunlint-code-quality/rules/go/S033-samesite-cookie.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S034-host-prefix-cookie.md +44 -0
- package/skill-assets/sunlint-code-quality/rules/go/S035-app-hostnames.md +50 -0
- package/skill-assets/sunlint-code-quality/rules/go/S036-internal-file-paths.md +56 -0
- package/skill-assets/sunlint-code-quality/rules/go/S037-anti-cache-headers.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S039-tls-certificate-validation.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/go/S041-logout-invalidation.md +46 -0
- package/skill-assets/sunlint-code-quality/rules/go/S042-long-lived-sessions.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S044-critical-changes-reauth.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S045-brute-force-protection.md +55 -0
- package/skill-assets/sunlint-code-quality/rules/go/S047-oauth-csrf-protection.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S048-oauth-redirect-validation.md +58 -0
- package/skill-assets/sunlint-code-quality/rules/go/S049-auth-code-expiry.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S050-token-entropy.md +53 -0
- package/skill-assets/sunlint-code-quality/rules/go/S051-password-length.md +49 -0
- package/skill-assets/sunlint-code-quality/rules/go/S052-otp-entropy.md +48 -0
- package/skill-assets/sunlint-code-quality/rules/go/S053-generic-error-messages.md +51 -0
- package/skill-assets/sunlint-code-quality/rules/go/S054-no-default-admin.md +43 -0
- package/skill-assets/sunlint-code-quality/rules/go/S055-content-type-validation.md +52 -0
- package/skill-assets/sunlint-code-quality/rules/go/S056-log-injection.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S057-synchronized-time.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/go/S058-ssrf-protection.md +70 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB001-use-snake-case.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB002-use-camel-case.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB003-use-screaming-snake-case.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB004-predicate-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB005-dangerous-methods.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB006-indentation.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB007-line-length.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB008-rescue-exception.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB009-save-bang.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB010-avoid-n-plus-one.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB011-use-find-each.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB012-sql-injection.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB013-prefer-has-many-through.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB014-dependent-associations.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB015-modern-validations.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB016-thin-controllers.md +45 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB017-avoid-fat-models.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB018-service-objects.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB019-avoid-metaprogramming.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB020-use-pluck.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB021-use-size.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB022-order-by-timestamps.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB023-where-missing.md +24 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB024-method-length.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB025-parameter-limits.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB026-avoid-deep-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB027-guard-clauses.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB028-class-length.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB029-meaningful-names.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB030-dry-principle.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB031-mvc-architecture.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB032-use-concerns.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB033-moderate-callbacks.md +31 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB034-use-decorators.md +33 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB035-comprehensive-tests.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB036-frozen-string-literal.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB037-it-parameter.md +25 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB038-modern-enum-syntax.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB039-solid-adapters.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB040-rails-authentication.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB041-async-query-loading.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB042-hotwire-turbo.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB043-use-propshaft.md +27 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB044-structured-logging.md +35 -0
- package/skill-assets/sunlint-code-quality/rules/ruby/RB045-prism-parser.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW001-block-based-kvo.md +40 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW002-class-delegate-protocol.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW003-compiler-protocol-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW004-contains-over-filter-count.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW005-convenience-type.md +34 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW006-discarded-notification-center-observer.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW007-discouraged-direct-init.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW008-discouraged-optional-boolean.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW009-empty-count.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW010-empty-string.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW011-explicit-init.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW012-fatal-error-message.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW013-for-where.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW014-force-cast.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW015-force-try.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW016-force-unwrapping.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW017-function-parameter-count.md +37 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW018-large-tuple.md +41 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW019-legacy-constructor.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW020-nesting.md +38 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW021-no-extension-access-modifier.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW022-overridden-super-call.md +30 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW023-override-in-extension.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW024-private-over-fileprivate.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW025-private-unit-test.md +32 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW026-prohibited-super-call.md +29 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW027-sorted-first-last.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW028-syntactic-sugar.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW029-unused-closure-parameter.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW030-unused-enumerated.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW031-unused-optional-binding.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW032-valid-ibinspectable.md +26 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW033-vertical-parameter-alignment.md +36 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW034-void-return.md +28 -0
- package/skill-assets/sunlint-code-quality/rules/swift/SW035-weak-delegate.md +28 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tránh khởi tạo trực tiếp các system types
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Tránh việc tạo các instance không hợp lệ hoặc không cần thiết của các system types có sẵn cơ chế factory/singleton.
|
|
5
|
+
tags: swift, ios, bundle, uidevice, initialization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tránh khởi tạo trực tiếp các system types
|
|
9
|
+
|
|
10
|
+
Tránh khởi tạo trực tiếp các kiểu như: `Bundle`, `NSError`, `UIDevice`. Sử dụng các factory method hoặc properties sẵn có (như `.main`, `.current`) thay vì gọi `init()` trực tiếp để đảm bảo tính nhất quán và hiệu năng.
|
|
11
|
+
|
|
12
|
+
**Incorrect (khởi tạo trực tiếp):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let bundle = Bundle()
|
|
16
|
+
let device = UIDevice()
|
|
17
|
+
let error = NSError()
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (sử dụng instance có sẵn):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
let mainBundle = Bundle.main
|
|
24
|
+
let currentDevice = UIDevice.current
|
|
25
|
+
let designError = NSError(domain: "com.example", code: 404, userInfo: nil) // Cần truyền tham số nếu tạo mới hợp lệ
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (discouraged_direct_init)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không dùng optional cho Boolean
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Tránh ambiguity logic (3 trạng thái: true, false, nil) khi chỉ cần 2 trạng thái logic nhị phân.
|
|
5
|
+
tags: swift, ios, boolean, optional, logic
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không dùng optional cho Boolean
|
|
9
|
+
|
|
10
|
+
Không khai báo kiểu `Bool?` nếu có thể tránh được. Việc có 3 trạng thái (`true`, `false`, `nil`) cho một biến logic thường gây khó khăn trong việc kiểm soát luồng điều kiện và dễ dẫn đến lỗi logic. Hãy sử dụng giá trị mặc định rõ ràng.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dùng optional bool):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
var isExpanded: Bool?
|
|
16
|
+
|
|
17
|
+
if isExpanded == true {
|
|
18
|
+
// ...
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (dùng bool với giá trị mặc định):**
|
|
23
|
+
|
|
24
|
+
```swift
|
|
25
|
+
var isExpanded: Bool = false
|
|
26
|
+
|
|
27
|
+
if isExpanded {
|
|
28
|
+
// ...
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** SwiftLint (discouraged_optional_boolean)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên .isEmpty thay vì .count == 0
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Code rõ ràng hơn và hiệu năng tốt hơn (một số collection cần duyệt để đếm nhưng chỉ cần check phần tử đầu để biết có trống không).
|
|
5
|
+
tags: swift, ios, performance, collection, isEmpty
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên .isEmpty thay vì .count == 0
|
|
9
|
+
|
|
10
|
+
Dùng `.isEmpty` giúp code dễ đọc hơn và mang ý nghĩa rõ ràng hơn về mục đích kiểm tra tập hợp trống. Ngoài ra, `.count == 0` có thể chậm hơn trên một số loại collection so với `.isEmpty`.
|
|
11
|
+
|
|
12
|
+
**Incorrect (kiểm tra count):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let items = [String]()
|
|
16
|
+
if items.count == 0 {
|
|
17
|
+
print("Danh sách trống")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (dùng isEmpty):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
let items = [String]()
|
|
25
|
+
if items.isEmpty {
|
|
26
|
+
print("Danh sách trống")
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (empty_count)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên isEmpty thay vì so sánh == ""
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Tăng độ rõ ràng và tránh lỗi tiềm ẩn khi xử lý chuỗi rỗng.
|
|
5
|
+
tags: swift, ios, string, isEmpty, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên isEmpty thay vì so sánh == ""
|
|
9
|
+
|
|
10
|
+
Dùng `.isEmpty` thay cho so sánh bằng chuỗi rỗng `""`. Điều này giúp code đồng bộ, chuyên nghiệp và rõ ràng hơn về mặt ngữ nghĩa trong Swift.
|
|
11
|
+
|
|
12
|
+
**Incorrect (so sánh chuỗi rỗng):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let name = ""
|
|
16
|
+
if name == "" {
|
|
17
|
+
print("Tên không được để trống")
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (dùng isEmpty):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
let name = ""
|
|
25
|
+
if name.isEmpty {
|
|
26
|
+
print("Tên không được để trống")
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (empty_string)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không sử dụng .init() khi không cần thiết
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Giảm bớt sự rườm rà trong code, tuân thủ phong cách viết Swift ngắn gọn và hiện đại.
|
|
5
|
+
tags: swift, ios, initialization, init, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không sử dụng .init() khi không cần thiết
|
|
9
|
+
|
|
10
|
+
Tránh gọi `.init()` một cách tường minh nếu không bắt buộc. Trong Swift, việc khởi tạo đối tượng bằng cách gọi trực tiếp tên kiểu dữ liệu kèm tham số được ưu tiên hơn vì tính ngắn gọn và dễ đọc.
|
|
11
|
+
|
|
12
|
+
**Incorrect (gọi .init tường minh):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let point = CGPoint.init(x: 10, y: 20)
|
|
16
|
+
let user = User.init(name: "John")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (khởi tạo trực tiếp):**
|
|
20
|
+
|
|
21
|
+
```swift
|
|
22
|
+
let point = CGPoint(x: 10, y: 20)
|
|
23
|
+
let user = User(name: "John")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** SwiftLint (explicit_init)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Luôn phải có message rõ ràng khi sử dụng fatalError
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Giúp xác định nguyên nhân lỗi nhanh chóng khi ứng dụng bị crash trong quá trình phát triển hoặc test.
|
|
5
|
+
tags: swift, ios, error-handling, fatalError, debugging
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Luôn phải có message rõ ràng khi sử dụng fatalError
|
|
9
|
+
|
|
10
|
+
Bắt buộc thêm mô tả khi gọi `fatalError(...)`. Message này rất quan trọng để cung cấp ngữ cảnh về lý do tại sao code rơi vào trạng thái không thể hồi phục, giúp trace lỗi dễ dàng hơn trong log hoặc khi debug.
|
|
11
|
+
|
|
12
|
+
**Incorrect (không có message):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
func test() {
|
|
16
|
+
fatalError()
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (có message mô tả):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
func test() {
|
|
24
|
+
fatalError("Hàm này chưa được triển khai hoặc trạng thái không hợp lệ")
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (fatal_error_message)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên for-where thay vì if trong loop
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Code gọn gàng hơn, giảm bớt một tầng lồng nhau (nesting) và thể hiện rõ ý định lọc phần tử.
|
|
5
|
+
tags: swift, ios, loops, for-where, readability
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên for-where thay vì if trong loop
|
|
9
|
+
|
|
10
|
+
Nếu chỉ có một điều kiện lọc duy nhất bên trong toàn bộ nội dung của vòng lặp, nên sử dụng cú pháp `for ... where` thay vì dùng câu lệnh `if` lồng bên trong. Điều này giúp mã nguồn phẳng hơn và làm rõ mục đích duyệt các phần tử thỏa mãn điều kiện.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dùng if lồng):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
for user in users {
|
|
16
|
+
if user.isActive {
|
|
17
|
+
print(user.name)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (dùng for-where):**
|
|
23
|
+
|
|
24
|
+
```swift
|
|
25
|
+
for user in users where user.isActive {
|
|
26
|
+
print(user.name)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (for_where)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tránh as! (force cast)
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Ngăn chặn crash ứng dụng do ép kiểu sai thực tế. Force-cast là nguyên nhân phổ biến gây lỗi runtime.
|
|
5
|
+
tags: swift, ios, casting, force-cast, safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tránh as! (force cast)
|
|
9
|
+
|
|
10
|
+
Không sử dụng ép kiểu bắt buộc `as!`, thay vào đó nên dùng `as?` kết hợp với kiểm tra null (optional binding). Force-cast sẽ khiến ứng dụng bị crash ngay lập tức nếu đối tượng không thuộc kiểu dữ liệu mong đợi.
|
|
11
|
+
|
|
12
|
+
**Incorrect (force cast):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! MyCustomCell
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct (safe cast):**
|
|
19
|
+
|
|
20
|
+
```swift
|
|
21
|
+
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MyCustomCell else {
|
|
22
|
+
fatalError("Không thể cast cell sang MyCustomCell")
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Tools:** SwiftLint (force_cast)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tránh try! (force try)
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Ngăn chặn crash ứng dụng khi có lỗi phát sinh từ các phương thức ném exception.
|
|
5
|
+
tags: swift, ios, error-handling, force-try, safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tránh try! (force try)
|
|
9
|
+
|
|
10
|
+
Tránh sử dụng `try!`, thay vào đó hãy dùng `try?` hoặc cấu trúc `do-catch`. `try!` bỏ qua việc xử lý lỗi và sẽ gây crash ứng dụng nếu có bất kỳ exception nào xảy ra trong quá trình thực thi.
|
|
11
|
+
|
|
12
|
+
**Incorrect (force try):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let content = try! String(contentsOfFile: filePath)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**Correct (xử lý lỗi an toàn):**
|
|
19
|
+
|
|
20
|
+
```swift
|
|
21
|
+
do {
|
|
22
|
+
let content = try String(contentsOfFile: filePath)
|
|
23
|
+
} catch {
|
|
24
|
+
print("Lỗi khi đọc file: \(error)")
|
|
25
|
+
}
|
|
26
|
+
// Hoặc
|
|
27
|
+
let content = try? String(contentsOfFile: filePath)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (force_try)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Tránh ! (force unwrap)
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Loại bỏ nguy cơ crash ứng dụng do truy cập trực tiếp vào giá trị nil của Optional.
|
|
5
|
+
tags: swift, ios, optional, force-unwrap, safety
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Tránh ! (force unwrap)
|
|
9
|
+
|
|
10
|
+
Không sử dụng `value!` để lấy giá trị từ một Optional. Force unwrap là một "code smell" nguy hiểm vì nó giả định giá trị luôn tồn tại. Ưu tiên dùng optional binding (`if let`, `guard let`) hoặc nil-coalescing operator (`??`) để xử lý an toàn.
|
|
11
|
+
|
|
12
|
+
**Incorrect (force unwrap):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let url = URL(string: "https://invalid url")!
|
|
16
|
+
print(url.absoluteString)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Correct (optional binding):**
|
|
20
|
+
|
|
21
|
+
```swift
|
|
22
|
+
if let url = URL(string: "https://example.com") {
|
|
23
|
+
print(url.absoluteString)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Hoặc dùng guard
|
|
27
|
+
guard let url = URL(string: "https://example.com") else {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** SwiftLint (force_unwrapping)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Giới hạn số lượng tham số của hàm dưới 6
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Giúp hàm dễ đọc, dễ kiểm thử và giảm độ phức tạp khi gọi hàm.
|
|
5
|
+
tags: swift, ios, clean-code, functions, architecture
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Giới hạn số lượng tham số của hàm dưới 6
|
|
9
|
+
|
|
10
|
+
Tránh khai báo function có quá nhiều tham số (nên giữ dưới 6). Một hàm có quá nhiều tham số thường vi phạm nguyên tắc Single Responsibility và gây khó khăn cho việc bảo trì. Nếu cần truyền nhiều dữ liệu, hãy cân nhắc gộp chúng vào một `struct` hoặc `class` chuyên biệt.
|
|
11
|
+
|
|
12
|
+
**Incorrect (quá nhiều tham số):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
func registerUser(name: String, age: Int, email: String, phone: String, city: String, country: String) {
|
|
16
|
+
// ...
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (gộp vào struct):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
struct UserRegistrationInfo {
|
|
24
|
+
let name: String
|
|
25
|
+
let age: Int
|
|
26
|
+
let email: String
|
|
27
|
+
let phone: String
|
|
28
|
+
let city: String
|
|
29
|
+
let country: String
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func registerUser(info: UserRegistrationInfo) {
|
|
33
|
+
// ...
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Tools:** SwiftLint (function_parameter_count)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không sử dụng tuple có quá nhiều phần tử
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Giảm độ phức tạp, tăng khả năng đọc hiểu (readability) và bảo trì (maintainability).
|
|
5
|
+
tags: swift, ios, tuple, struct, code-quality
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không sử dụng tuple có quá nhiều phần tử
|
|
9
|
+
|
|
10
|
+
Tuple chỉ nên được sử dụng cho các nhóm dữ liệu nhỏ, tạm thời (tối đa 2-3 phần tử). Nếu một tuple dài hơn, người đọc sẽ khó biết được ý nghĩa của từng phần tử qua index. Trong trường hợp này, hãy thay thế bằng một `struct` có tên thuộc tính rõ ràng.
|
|
11
|
+
|
|
12
|
+
**Incorrect (tuple lớn):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
func getInfo() -> (String, Int, Double, String) {
|
|
16
|
+
return ("John", 30, 180.5, "New York")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let info = getInfo()
|
|
20
|
+
print(info.0) // Khó hiểu index 0 là gì
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Correct (dùng struct):**
|
|
24
|
+
|
|
25
|
+
```swift
|
|
26
|
+
struct PersonInfo {
|
|
27
|
+
let name: String
|
|
28
|
+
let age: Int
|
|
29
|
+
let height: Double
|
|
30
|
+
let city: String
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func getInfo() -> PersonInfo {
|
|
34
|
+
return PersonInfo(name: "John", age: 30, height: 180.5, city: "New York")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let info = getInfo()
|
|
38
|
+
print(info.name) // Rõ ràng và dễ hiểu
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Tools:** SwiftLint (large_tuple)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Sử dụng khởi tạo Swift thay vì Objective-C
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Giữ cho mã nguồn Swift thuần túy (idiomatic), hiện đại và dễ bảo trì.
|
|
5
|
+
tags: swift, ios, legacy, objective-c, initialization
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Sử dụng khởi tạo Swift thay vì Objective-C
|
|
9
|
+
|
|
10
|
+
Không sử dụng các hàm khởi tạo kiểu cũ (legacy constructors) kế thừa từ Objective-C như `CGPointMake`, `CGRectMake`, `CGSizeMake`, v.v. Hãy sử dụng các hàm khởi tạo chính thức của Swift để tận dụng các tính năng của ngôn ngữ như tham số có tên (named parameters).
|
|
11
|
+
|
|
12
|
+
**Incorrect (Legacy constructor):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let point = CGPointMake(10, 20)
|
|
16
|
+
let rect = CGRectMake(0, 0, 100, 100)
|
|
17
|
+
let size = CGSizeMake(50, 50)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (Swift constructor):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
let point = CGPoint(x: 10, y: 20)
|
|
24
|
+
let rect = CGRect(x: 0, y: 0, width: 100, height: 100)
|
|
25
|
+
let size = CGSize(width: 50, height: 50)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (legacy_constructor)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Data type chỉ lồng nhau tối đa 1 level
|
|
3
|
+
impact: LOW
|
|
4
|
+
impactDescription: Tránh mã nguồn khó đọc và khó debug do có quá nhiều tầng lồng nhau.
|
|
5
|
+
tags: swift, ios, nesting, architecture, clean-code
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Data type chỉ lồng nhau tối đa 1 level
|
|
9
|
+
|
|
10
|
+
Không nên lồng quá một cấp trong các định nghĩa kiểu dữ liệu như `class`, `struct`, `enum`, `actor`. Việc lồng nhau quá sâu làm tăng độ phức tạp của tên kiểu dữ liệu (fully qualified name) và thường vi phạm nguyên tắc Phân tách mối quan tâm (Separation of Concerns).
|
|
11
|
+
|
|
12
|
+
**Incorrect (lồng quá sâu):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
class Parent {
|
|
16
|
+
class Child {
|
|
17
|
+
class GrandChild {
|
|
18
|
+
// ...
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Correct (phẳng hóa hoặc chỉ lồng 1 cấp):**
|
|
25
|
+
|
|
26
|
+
```swift
|
|
27
|
+
class Parent {
|
|
28
|
+
class Child {
|
|
29
|
+
// ...
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class GrandChild {
|
|
34
|
+
// ...
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Tools:** SwiftLint (nesting)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không sử dụng access modifier với extension
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Giữ cho extension rõ ràng và nhất quán theo chuẩn Swift style guide.
|
|
5
|
+
tags: swift, ios, extension, access-modifier, style-guide
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không sử dụng access modifier với extension
|
|
9
|
+
|
|
10
|
+
Không nên khai báo `public`, `private`, `internal`, hoặc `fileprivate` trực tiếp cho phần `extension`. Quyền truy cập nên được định nghĩa cụ thể cho từng thành phần (biến, hàm) bên trong extension. Điều này giúp kiểm soát truy cập linh hoạt hơn và tránh các hành vi không mong muốn khi gom nhóm code.
|
|
11
|
+
|
|
12
|
+
**Incorrect (khai báo modifier cho extension):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
public extension MyClass {
|
|
16
|
+
func doSomething() {}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (khai báo modifier cho thành phần bên trong):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
extension MyClass {
|
|
24
|
+
public func doSomething() {}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (no_extension_access_modifier)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Gọi super trong các phương thức lifecycle
|
|
3
|
+
impact: HIGH
|
|
4
|
+
impactDescription: Đảm bảo các hành vi mặc định của hệ thống được thực thi đúng cách, tránh lỗi UI hoặc logic không đồng bộ.
|
|
5
|
+
tags: swift, ios, uikit, lifecycle, super-call
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Gọi super trong các phương thức lifecycle
|
|
9
|
+
|
|
10
|
+
Khi override các phương thức lifecycle của hệ thống (như `viewDidLoad`, `viewWillAppear`, `viewDidAppear`, `viewWillDisappear`, v.v. trong UIKit), bạn bắt buộc phải gọi `super.methodName()`. Việc thiếu lời gọi tới lớp cha có thể khiến các khởi tạo mặc định hoặc logic quan trọng của hệ thống không được thực hiện, dẫn đến các lỗi khó xác định.
|
|
11
|
+
|
|
12
|
+
**Incorrect (không gọi super):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
override func viewWillAppear(_ animated: Bool) {
|
|
16
|
+
// Thiếu super.viewWillAppear(animated)
|
|
17
|
+
setupTheme()
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (gọi super đầy đủ):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
override func viewWillAppear(_ animated: Bool) {
|
|
25
|
+
super.viewWillAppear(animated)
|
|
26
|
+
setupTheme()
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Tools:** SwiftLint (overridden_super_call)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không sử dụng override trong extension
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Tránh thay đổi hành vi định nghĩa ban đầu của lớp cha qua extension, giữ code ổn định và dễ dự đoán.
|
|
5
|
+
tags: swift, ios, extension, override, architecture
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không sử dụng override trong extension
|
|
9
|
+
|
|
10
|
+
Không được `override` thuộc tính hoặc phương thức của lớp cha bên trong một `extension`. Extension được thiết kế để mở rộng tính năng chứ không phải để thay đổi hành vi thừa kế. Nếu cần override, hãy thực hiện việc này trong định nghĩa của chính class đó hoặc class con.
|
|
11
|
+
|
|
12
|
+
**Incorrect (override trong extension):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
extension MyViewController {
|
|
16
|
+
override func viewDidLoad() { // Nguy hiểm: Không nên override ở đây
|
|
17
|
+
super.viewDidLoad()
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (override trong class chính):**
|
|
23
|
+
|
|
24
|
+
```swift
|
|
25
|
+
class MyViewController: UIViewController {
|
|
26
|
+
override func viewDidLoad() {
|
|
27
|
+
super.viewDidLoad()
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** SwiftLint (override_in_extension)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên private thay vì fileprivate
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Giới hạn phạm vi truy cập chặt chẽ nhất có thể, giúp tăng tính đóng gói (encapsulation).
|
|
5
|
+
tags: swift, ios, access-control, private, fileprivate
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên private thay vì fileprivate
|
|
9
|
+
|
|
10
|
+
Sử dụng `private` để giới hạn phạm vi truy cập trong cùng một khối khai báo `class`/`struct`. Chỉ sử dụng `fileprivate` nếu bạn thực sự cần chia sẻ dữ liệu giữa các class/struct khác nhau cùng nằm trong một file vật lý Duy nhất. Việc lạm dụng `fileprivate` làm loãng tính đóng gói của code.
|
|
11
|
+
|
|
12
|
+
**Incorrect (dùng fileprivate khi không cần thiết):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
class User {
|
|
16
|
+
fileprivate var name: String = ""
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (dùng private):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
class User {
|
|
24
|
+
private var name: String = ""
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (private_over_fileprivate)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không khai báo private cho Unit Test function
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Đảm bảo các hàm test có thể được nhận diện và thực thi bởi trình chạy test (test runner).
|
|
5
|
+
tags: swift, ios, unit-test, xctest, access-control
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không khai báo private cho Unit Test function
|
|
9
|
+
|
|
10
|
+
Không sử dụng `private` cho các class hoặc function bên trong `XCTestCase`. Các hàm test cần có quyền truy cập `internal` (mặc định) hoặc `public` để Swift test runner có thể gọi và thực thi chúng. Nếu đánh dấu là `private`, các bài test này sẽ bị bỏ qua một cách lặng lẽ.
|
|
11
|
+
|
|
12
|
+
**Incorrect (test function bị private):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
class MyTests: XCTestCase {
|
|
16
|
+
private func testLoginSuccess() { // Lỗi: Test runner không tìm thấy
|
|
17
|
+
// ...
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Correct (hàm test công khai):**
|
|
23
|
+
|
|
24
|
+
```swift
|
|
25
|
+
class MyTests: XCTestCase {
|
|
26
|
+
func testLoginSuccess() { // Đúng: Mặc định là internal
|
|
27
|
+
// ...
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Tools:** SwiftLint (private_unit_test)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Không gọi super trong các specific method
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Tránh các hành vi không mong muốn hoặc crash khi gọi super trong các hàm mà Apple khuyến nghị không nên gọi.
|
|
5
|
+
tags: swift, ios, uikit, super-call, prohibited-call
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Không gọi super trong các specific method
|
|
9
|
+
|
|
10
|
+
Không nên gọi `super` nếu phương thức không định nghĩa gì trong lớp cha hoặc bị Apple khuyến nghị không gọi trong các phương thức cụ thể (ví dụ: `loadView()`). Việc gọi `super.loadView()` có thể gây ra các vòng lặp khởi tạo vô hạn hoặc chiếm dụng tài nguyên không cần thiết.
|
|
11
|
+
|
|
12
|
+
**Incorrect (gọi super trong loadView):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
override func loadView() {
|
|
16
|
+
super.loadView() // Không cần thiết và không được khuyến khích
|
|
17
|
+
view = MyCustomView()
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (không gọi super):**
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
override func loadView() {
|
|
25
|
+
view = MyCustomView()
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Tools:** SwiftLint (prohibited_super_call)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Ưu tiên dùng .min() hoặc .max() thay vì sorted().first/last
|
|
3
|
+
impact: CRITICAL
|
|
4
|
+
impactDescription: Tăng hiệu năng đáng kể (O(n) so với O(n log n)) khi tìm phần tử lớn nhất/nhỏ nhất trong collection.
|
|
5
|
+
tags: swift, ios, performance, algorithms, collection
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ưu tiên dùng .min() hoặc .max() thay vì sorted().first/last
|
|
9
|
+
|
|
10
|
+
Sử dụng `.min()` hoặc `.max()` thay vì `.sorted().first` hoặc `.sorted().last`. Việc gọi `sorted()` sẽ phải sắp xếp toàn bộ tập hợp (độ phức tạp O(n log n)), trong khi `.min()`/`.max()` chỉ duyệt qua tập hợp đúng 1 lần (độ phức tạp O(n)). Điều này giúp tăng hiệu năng rõ rệt trên các tập dữ liệu lớn.
|
|
11
|
+
|
|
12
|
+
**Incorrect (sort rồi lấy phần tử đầu/cuối):**
|
|
13
|
+
|
|
14
|
+
```swift
|
|
15
|
+
let numbers = [5, 3, 8, 1, 9]
|
|
16
|
+
let minNumber = numbers.sorted().first
|
|
17
|
+
let maxNumber = numbers.sorted().last
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Correct (dùng hàm chuyên dụng):**
|
|
21
|
+
|
|
22
|
+
```swift
|
|
23
|
+
let numbers = [5, 3, 8, 1, 9]
|
|
24
|
+
let minNumber = numbers.min()
|
|
25
|
+
let maxNumber = numbers.max()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Tools:** SwiftLint (sorted_first_last)
|