You are currently working with Swift code within a Swift package. Below are common anti-patterns that agents tend to follow, and what you should do instead. Make sure to generate code in correspondence with examples listed as GOOD or OK, and avoid generating code listed as a BAD example.
struct Foo {}
// GOOD
let foo = Foo()
// BAD
let foo: Foo = .init()// GOOD
let arr = [UInt8]()
let dict = [String: Int]()
// BAD
let arr: [UInt8] = []
let dict: [String: Int] = [:]struct Item {
let property: String
}
// GOOD
extension Item {
var propertyCount: Int {
self.property.count
}
}
// BAD
extension Item {
var propertyCount: Int {
property.count
}
}struct Item {
static let property = "..."
}
// GOOD
extension Item {
static var propertyCount: Int {
Self.property.count
}
}
// BAD
extension Item {
static var propertyCount: Int {
property.count
}
}
extension Item {
static var propertyCount: Int {
self.property.count
}
}Avoid nesting initial assignments inside nested blocks such as in conditional and switch statements.
// GOOD
func foo() {
let property = bar()
// ...
}
func bar() -> Value {
if someCondition {
currentValue
} else {
previousValue
}
}
// BAD
func foo() {
let property: Value
if someCondition {
property = currentValue
} else {
property = previousValue
}
// ...
}// GOOD
func value() -> Value {
switch statement {
case 1: 1
case 4: 16
default: 64
}
}
func value() -> Value {
if statement == 1 {
1
} else if statement == 4 {
16
} else {
64
}
}
// BAD
func value() -> Value {
switch statement {
case 1: return 1
case 4: return 16
default: return 64
}
}
func value() -> Value {
if statement == 1 {
return 1
} else if statement == 4 {
return 16
} else {
return 64
}
}struct Value {}
// GOOD
extension Value {
func foo() {
// ...
self.bar()
// ...
}
private func bar() {
// ...
}
}
// BAD
extension Value {
func foo() {
// ...
Self.bar()
// ...
}
private static func bar() {
// ...
}
}// GOOD
let transformedItems = items.compactMap {
guard $0.count > 0 else { return nil }
return TransformedItem($0)
}
let filteredItems = items.filter { $0.count > 0 }
// BAD
var transformedItems = [TransformedItem]()
for item in items {
guard item.count > 0 else { continue }
transformedItems.append(TransformedItem(item))
}
var filteredItems = [Item]()
for item in items {
guard item.count > 0 else { continue }
filteredItems.append(item)
}struct Item {
let name: String?
}
// GOOD
func updatedName(item: Item) -> String? {
item.name.map { $0 + " Updated" }
}
// BAD
func updatedName(item: Item) -> String? {
guard let name = item.name else { return nil }
return name + " Updated"
}// GOOD
struct Item: Hashable, Sendable {
let name: String
let quantity: Int
}
// BAD
struct Item {
let name: String
let quantity: Int
}struct Item: Hashable, Sendable {
let name: String
let quantity: Int
}
// GOOD
extension Item {
init(name: String) {
self.init(name: name, quantity: 0)
}
}
// BAD
extension Item {
init(name: String) {
self.name = name
self.quantity = 0
}
}Avoid conforming to @unchecked Sendable, and try to always make a type conform to Sendable proper instead. If you must conform to @unchecked Sendable, then make sure to leave a comment explaining why it is safe to do so.
// GOOD
final class Item: Sendable {
let count = Mutex(0)
// ...
}
// OK (but not always ideal, avoid if possible)
final class Item: @unchecked Sendable {
// NB: @unchecked Sendable is safe because we use a lock.
private let lock = NSLock()
var count = 0
// ...
}
// BAD
final class Item: @unchecked Sendable {
var count = 0
// ...
}Task.detached is rarely useful, and can almost always be expressed in simpler ways. Prefer traditional Task initializations instead.
// GOOD
Task { await work() }
// BAD
Task.detached { await work() }If the sending keyword and nonisolated(nonsending) directive can be used to solve a concurrency error related to a non-Sendable value, then use it instead of conforming the type to Sendable.
final class NonSendable {}
// GOOD
func work(value: sending NonSendable) {
Task { await value.workAsync() }
}
extension NonSendable {
nonisolated(nonsending) func workAsync() async {
// ...
}
}
// BAD
extension NonSendable: Sendable {
func workAsync() async {
// ...
}
}
func work(value: NonSendable) {
Task { await value.workAsync() }
}If you need to conform an existing non-Sendable type to Sendable, consider creating a small wrapper that conforms to Sendable, and that wraps the non-Sendable core. this way, you push the need for Sendable away as far as possible. Generally, follow the principles in this article.
Use Swift Testing by default. You should only fallback to XCTest if you must write a test that request an XCTestExpectation.
// GOOD
import Testing
@Test
func `My Work Does Something`() {
// ...
}
// OK (Only if you need to use XCTestExpectation)
import XCTest
final class MyTestSuite {
func testMyWorkDoesSomething() async {
let expectation = self.expectation(description: "Finishes")
// ...
}
}
// BAD
import XCTest
final class MyTestSuite {
func testMyWorkDoesSomething() {
// ...
}
}If CustomDump is added as a dependency, prefer expectNoDifference instead of the #expect macro from Swift testing.
// GOOD
import CustomDump
@Test
func `Matches`() {
let v1 = NyEquatableValue(value: 10)
let v2 = MyEquatableValue(value: 10)
expectNoDifference(v1, v2)
}
// BAD
@Test
func `Matches`() {
let v1 = NyEquatableValue(value: 10)
let v2 = MyEquatableValue(value: 10)
#expect(v1 == v2)
}// GOOD
@Test
func `This Is A Test Of Something`() {
// ...
}
// BAD
@Test
func `this is a test of something`() {
// ...
}// GOOD
@Test
func `Work Throws`() {
#expect(throws: WorkError.self) {
try work()
}
}
// BAD
@Test
func `Work Throws`() {
do {
try work()
Issue.record("Expected error but none thrown.")
} catch {
expectNoDifference(error is WorkError, true)
}
}// GOOD
@Suite
struct `MyValue tests` {
// ...
}
// BAD
struct MyValueTests {
// ...
}If you run into a build error in a package with at least 1 macro target that states that the “SwiftCompilerPlugin” cannot be found, do the following:
- Fully delete the
.builddirectory (rm -drf .build). - Rerun
swift build --build-tests --disable-experimental-prebuilts. - Then proceed with the build/test/run command you were attempting before.
You should also run this flow if you ever run swift package clean.