Yeni diagnostic architecture

Swift, sınıf mirası, protokol uygunlukları, jeneriklik ve aşırı yükleme gibi birçok özelliğe sahip zengin bir tip sistemiyle çok etkileyici bir dildir.
Swift, sınıf mirası, protokol uygunlukları, jeneriklik ve aşırı yükleme gibi birçok özelliğe sahip zengin bir tip sistemiyle çok etkileyici bir dildir.

Teşhis, programlama dili deneyiminde çok önemli bir rol oynar. Geliştiricinin üretkenliği için, derleyicinin, özellikle eksik veya geçersiz kod olmak üzere, her durumda uygun rehberlik üretmesi hayati öneme sahiptir.

Bu blog yazısında, yaklaşan Swift 5.2 sürümü için üzerinde çalışılan diyagnostikler konusundaki iyileştirmelerle ilgili birkaç önemli güncellemeyi paylaşmak istiyoruz. Bu, derleyicideki başlangıçta Swift 5.1 sürümünün bir parçası olarak tanıtılan ve bazı heyecan verici yeni sonuçlar ve iyileştirilmiş hata mesajları veren arızaların teşhisi için yeni bir strateji içerir.

Meydan okuma

Swift, sınıf mirası, protokol uygunlukları, jeneriklik ve aşırı yükleme gibi birçok özelliğe sahip zengin bir tip sistemiyle çok etkileyici bir dildir. Programcılar olarak iyi biçimlendirilmiş programlar yazmak için elimizden gelenin en iyisini yapmaya çalışsak da, bazen biraz yardıma ihtiyacımız var. Neyse ki, derleyici tam olarak hangi Swift kodunun geçerli ve geçersiz olduğunu biliyor. Sorun size neyin yanlış gittiğini, nerede olduğunu ve nasıl düzeltebileceğinizi söyleyebilmek için en iyisidir.

Derleyicinin çoğu kısmı programınızın doğruluğunu sağlar, ancak bu çalışmanın odağı tip denetleyicisini iyileştirmek olmuştur . Swift tipi denetleyicisi, türlerin kaynak kodunda nasıl kullanıldığına ilişkin kuralları uygular ve bu kuralların ne zaman ihlal edildiğini size bildirmekten sorumludur.

Örneğin, aşağıdaki kod:

struct S<T> {
  init(_: [T]) {}
}

var i = 42
_ = S<Int>([i!])

Aşağıdaki tanılamayı üretir:

error: type of expression is ambiguous without more context

Bu tanılama gerçek bir hatayı işaret ederken, belirli ya da işlem yapılmadığı için yardımcı değildir. Bunun nedeni, eski tip denetleyicisinin bir hatanın tam yerini tahmin etmek için kullanılmasıdır . Bu birçok durumda işe yaradı, ancak kullanıcıların doğru bir şekilde tanımlayamadıkları bir çok yazım hatası vardı. Bunu ele almak için, çalışmalarda yeni bir teşhis altyapısı var. Bir hatanın nerede oluştuğunu tahmin etmekten ziyade, tip denetleyicisi, uyguladığı düzeltmeleri hatırlarken problemleri tam olarak karşılaştıkları noktada “çözmeye” çalışır. Bu sadece tip denetleyicisinin daha fazla program türündeki hataları saptamasına izin vermekle kalmaz, aynı zamanda ilk hatayı bildirdikten sonra daha önce duracağı daha fazla hatayla yüzleşmesini sağlar.

Tür Çıkarımına Genel Bakış

Yeni teşhis altyapısı tip denetleyicisi ile sıkı sıkıya bağlantılı olduğundan, kısa bir yol izlememiz ve tip çıkarımı hakkında konuşmamız gerekir. Bunun kısa bir giriş olduğunu unutmayın; Daha fazla ayrıntı için lütfen derleyicinin tip denetleyicisinin belgelerine bakın .

Swift, klasik Hindley-Milner türünden çıkarım algoritmasını hatırlatan kısıtlamaya dayalı bir tür denetleyicisi kullanarak iki yönlü tür çıkarımı uygular :

  • Type checker, kaynak kodunu , koddaki türler arasındaki ilişkileri temsil eden bir kısıtlama sistemine dönüştürür .
  • Bir tür ilişkisi, ya tek bir türe (örneğin, tamsayı bir tamsayı türüdür) gereksinim koyan veya iki tür (örneğin biri diğerine dönüştürülebilir) olan bir tür kısıtlamasıyla ifade edilir .
  • Sınırlamalarda açıklanan tipler, tuple tipleri, fonksiyon tipleri, enum / struct / class tipleri, protokol tipleri ve jenerik tipler dahil olmak üzere Swift tipindeki herhangi bir tip olabilir. Ek olarak, bir tür, olarak belirtilen bir tür değişkeni olabilir $<name>.
  • Tip değişkenleri, başka bir türün yerine, örneğin ($Foo, Int)tip değişkenini içeren bir tür tipinde kullanılabilir $Foo.

Kısıtlama Sistemi üç adım gerçekleştirir:

  1. Kısıt Üretimi
  2. Kısıtlama Çözümü
  3. Çözüm Uygulaması

Teşhis için, sadece ilginç aşamalar Kısıt Üretimi ve Çözümü.

Bir girdi ifadesi verildiğinde (ve bazen ek bağlamsal bilgiler), kısıtlayıcı çözücü oluşturur:

  1. Her alt ifadenin soyut türünü temsil eden bir tür değişkenleri kümesi
  2. Bu değişkenler arasındaki ilişkileri tanımlayan bir dizi kısıtlama.

En yaygın kısıtlama türü, iki türü ilişkilendiren ve şu şekilde belirtilen ikili bir kısıtlamadır :

type1 <constraint kind> type2

Yaygın olarak kullanılan ikili kısıtlamalar:

  1. $X <bind to> Y– Tür değişkenini $Xsabit bir türe bağlarY
  2. X <convertible to> Y– Bir dönüşüm kısıtlaması , alt tip ve eşitlik içeren birinci tipin Xikinciye dönüştürülebilir olmasını gerektirir.Y
  3. X <conforms to> Y– İlk tipin Xprotokole uygun olması gerektiğini belirtirY
  4. (Arg1, Arg2, ...) → Result <applicable to> $Function – “Uygulanabilir bir fonksiyon” kısıtlaması, her iki tipin de aynı giriş ve çıkış tiplerine sahip fonksiyon tipleri olmasını gerektirir.

Kısıt oluşturma işlemi tamamlandığında, çözücü kısıtlama sistemindeki her bir değişkene somut tipler atamaya ve tüm kısıtlamaları karşılayan bir çözüm oluşturmaya çalışır.

Aşağıdaki örnek işlevi düşünelim:

func foo(_ str: String) {
  str + 1
}

Bir insan için, ifade ile ilgili bir sorun str + 1olduğu ve bu sorunun bulunduğu yer oldukça hızlı bir şekilde ortaya çıkıyor , ancak çıkarım motoru yalnızca neyin yanlış olduğunu belirlemek için bir kısıtlama basitleştirme algoritmasına güvenebilir.

Daha önce kurulmuş gibi, üretim kısıtlamalar tarafından kısıtlama çözücü başlar (bakınız Kısıtlama Üretimi için sahne) str1ve +. Giriş ifadesinin her bir ayrı alt öğesi, örneğin str:

  1. Somut bir tip (önceden bilinen)
  2. $<name>kendisiyle ilişkilendirilen kısıtlamaları sağlayan herhangi bir türü alabilen bir tür değişken .

Kısıt Üretimi aşaması tamamlandıktan sonra, ifade için kısıt sistemi str + 1tür değişkenleri ve kısıtlamaların bir kombinasyonuna sahip olacaktır. Şunlara şimdi bakalım.

Tür Değişkenleri

  • $Strstrçağrının ilk argümanı olan değişken türünü gösterir .+
  • $Oneçağrıdaki 1ikinci argüman olan değişmez türünü temsil eder .+
  • $Result operatöre yapılan çağrının sonuç türünü gösterir. +
  • $Plus+denemek için olası bir aşırı yükleme seçeneği kümesi olan operatörün türünü temsil eder .

Kısıtlamalar

  • $Str <bind to> String
    • Bağımsız değişken strsabit bir Dize türüne sahiptir.
  • $One <conforms to> ExpressibleByIntegerLiteral
    • Swift’deki tamsayı değişmezleri 1ExpressibleByIntegerLiteral protokolüne (örneğin Intveya Double) uyan herhangi bir türü kabul edebileceğinden , çözücü yalnızca başlangıçta bu bilgilere güvenebilir.
  • $Plus <bind to> disjunction((String, String) -> String, (Int, Int) -> Int, ...)
    • Operatör , her bir elemanın bireysel aşırı yüklenme türünü temsil ettiği ayrık+ bir seçenekler kümesi oluşturur.
  • ($Str, $One) -> $Result <applicable to> $Plus
    • Türü $Resulthenüz bilinmemektedir; $Plusargüman demeti ile her aşırı yüklemeyi test ederek belirlenir ($Str, $One).

Tüm kısıtlamaların ve tür değişkenlerinin giriş ifadesindeki belirli konumlarla bağlantılı olduğunu unutmayın:

Çıkarım algoritması kısıtlama sistemindeki tüm tip değişkenleri için uygun tipler bulmaya ve bunları ilgili kısıtlamalara karşı test etmeye çalışır. Örneğimizde, $Onebir tür alabilir Intveya Doublebu türlerin her ikisi de ExpressibleByIntegerLiteralprotokol uygunluk gerekliliklerini yerine getirdiği için olabilir. Bununla birlikte, kısıtlama sistemindeki her “boş” tip değişkeninin her biri için olası tüm tipleri sıralamak yeterli değildir çünkü belirli bir tip değişkeninin kısıtlı olduğu durumlarda denenebilecek birçok tip olabilir . Örneğin,$ResultKısıtlamaları yoktur, bu nedenle herhangi bir tür potansiyel olarak varsayılabilir. Bu soruna geçici bir çözüm bulmak için, kısıtlayıcı çözücüsü önce çözücünün dahil olan her tür değişkeni için olası tür kümesini daraltmasına izin veren ayrılma seçeneklerini dener. Bu durumda, $Resultolası tiplerin sayısını sadece $Plustüm olası tiplerin yerine aşırı yük seçenekleriyle ilgili sonuç tiplerine düşürür .

Şimdi, $Oneve için türlerini belirlemek üzere çıkarım algoritmasını çalıştırmanın zamanı geldi $Result.

Tek Bir Çıkarım Algoritması Uygulaması Turu:

  1. $Plusİlk ayrılma seçimine bağlanarak başlayalım .(String, String) -> String
  2. Şimdi applicable tokısıtlama test edilebildi, çünkü $Plussomut bir tipe bağlıydı. Sadeleştirilmesi ($Str, $One) -> $Result <applicable to> $Pluskısıt uçları iki fonksiyon türlerini eşleştirme yukarı ($Str, $One) -> $Resultve (String, String) -> Stringaşağıda ki ilerler:
    • Parametre 0 ile 0 parametresini eşleştirmek için yeni bir dönüşüm kısıtı ekleyin – $Str <convertible to> String
    • Parametre 1 ile argüman 1’i eşleştirmek için yeni bir dönüşüm kısıtı ekleyin – $One <convertible to> String
    • Equate $Resultiçin Stringsonuç tipleri eşit olmak zorunda çünkü
  3. Yeni oluşturulan kısıtlamaların bazıları hemen test edilebilir / basitleştirilebilir;
    • $Str <convertible to> Stringolduğu true, çünkü $Strzaten sabit bir türü olan Stringve Stringkendisine dönüştürülebilen
    • $ResultStringeşitlik kısıtına dayalı bir tür tahsis edilebilir
  4. Bu noktada sadece kalan kısıtlamalar şunlardır:
    • $One <convertible to> String
    • $One <conforms to> ExpressibleByIntegerLiteral
  5. İçin olası türleri $Onevardır IntDoubleve String. Bu ilginç, çünkü bu olası türlerin hiçbiri kalan tüm kısıtlamaları yerine getirmiyor; Intve Doubleher ikisi de dönüştürülemez Stringve protokole Stringuygun değilExpressibleByIntegerLiteral
  6. Tüm olası tipler denendikten sonra $One, çözücü durur ve mevcut tipler setini ve aşırı yük seçimini bir başarısızlık olarak değerlendirir. Çözücü daha sonra geri izler ve bir sonraki ayrılma seçimini dener $Plus.

Hata konumunun çözücü tarafından çıkarım algoritmasını yürütürken belirleneceğini görüyoruz. Olası türlerden hiçbiri eşleşmediğinden, $Onebir hata yeri olarak düşünülmelidir (çünkü herhangi bir türe bağlanamaz). Karmaşık ifadeler, bu gibi birden fazla konuma sahip olabilir çünkü mevcut hatalar çıkarım algoritması ilerledikçe yeni sonuçlara yol açar. Böyle durumlarda hata konumlarını daraltmak için, çözücü yalnızca mümkün olan en az sayıda çözümü seçer.

Bu noktada, hata konumlarının nasıl tanımlandığı az ya da çok açıktır, ancak çözücünün bu tür senaryolarda ilerleme kaydetmesine nasıl yardım edeceği henüz tam bir çözüm elde edilemez.

Yaklaşım

Yeni teşhis altyapısı, çözücünün denemek için başka hiçbir türle takılmadığı tutarsız durumları denemek ve çözmek için bir kısıtlama düzeltmesi olarak adlandırdığımız şeyi kullanır . Örneğimizin düzeltmesi StringExpressibleByIntegerLiteralprotokole uymayan ihmal etmektir . Bir düzeltmenin amacı, hata yeri hakkındaki tüm yararlı bilgileri çözücüden yakalayabilmek ve daha sonra teşhis için kullanabilmektir. Mevcut ve yeni yaklaşımlar arasındaki temel fark budur. Birincisi , hatanın nerede olduğunu, yeni yaklaşımın, hata konumlarının tümünü sağlayan çözücüyle simbiyotik bir ilişkiye sahip olduğunu tahmin etmeye çalışır.

Daha önce belirttiğimiz gibi, tüm tip değişkenleri ve kısıtlamaları, kaynaklandığı alt ifade ile ilişkileri hakkında bilgi taşır. Tip bilgisi ile birleştirilen böyle bir ilişki, yeni teşhis çerçevesi ile teşhis edilen tüm sorunlara uyarlanmış teşhis ve düzeltmeyi sağlamayı kolaylaştırır.

Örneğimizde, tür değişkeninin $Onebir hata yeri olduğu belirlendi , bu nedenle tanı $Onegiriş ifadesinde nasıl kullanıldığını inceleyebilir : $Oneişleve çağrıda # 2 konumundaki bir argümanı temsil eder +ve sorunun ilişkili olduğu bilinmektedir protokole Stringuygun olmayan gerçeğine ExpressibleByIntegerLiteral. Tüm bu bilgilere dayanarak aşağıdaki iki teşhisden birini oluşturmak mümkündür:

error: binary operator '+' cannot be applied to arguments 'String' and 'Int'

ikinci argüman hakkında ExpressibleByIntegerLiteralprotokole uymayan veya daha basit olan bir not :

error: argument type 'String' does not conform to 'ExpressibleByIntegerLiteral'

ikinci argümana atıfta bulunarak.

İlk alternatifi seçtik ve operatör hakkında bir teşhis ve her kısmen eşleşen aşırı yükleme seçeneği için bir not hazırladık. Tanımlanan yaklaşımın iç çalışmalarına daha yakından bir göz atalım.

Teşhis Anatomisi

Bir sınırlama hatası tespit edildiğinde, bir hatayla ilgili bilgileri yakalayan bir sınırlama düzeltmesi oluşturulur:

  • Meydana gelen başarısızlık türü
  • Kaynak kodda, hatanın geldiği yer
  • Arızada yer alan türler ve beyanlar

Kısıt çözücü bu düzeltmeleri biriktirir. Bir çözüme ulaştıktan sonra, çözümün bir parçası olan düzeltmelere bakar ve uygulanabilir hatalar veya uyarılar oluşturur. Tüm bunların birlikte nasıl çalıştığına bir göz atalım. Aşağıdaki örneği düşünün:

func foo(_: inout Int) {}

var x: Int = 0
foo(x)

Buradaki sorun, açıkça bir parametreye parametresine xargüman olarak geçilemeyen bir argümanla ilgilidir .inout&

Şimdi bu kısıtlama sisteminin tip değişkenlerine ve kısıtlamalarına bakalım.

Tür Değişkenleri

Üç tür değişken vardır:

$X := Int
$Foo := (inout Int) -> Void
$Result

Kısıtlamalar

Üç tip değişkeni aşağıdaki kısıtlamalara sahiptir:

($X) -> $Result <applicable to> $Foo

Çıkarsama algoritması denemek ve maç gidiyor ($X) -> $Resultiçin (inout Int) -> Voidaşağıdaki yeni kısıtlamalar hangi sonuçlara:

Intdönüştürülemez inout Int, bu nedenle kısıtlayıcı çözücü arızayı eksik& olarak kaydeder ve <convertible to>kısıtlamayı yok sayar .

Bu kısıtlama dikkate alınmadığında, kısıtlama sisteminin geri kalanı çözülebilir. Ardından, tür denetleyicisi kaydedilen düzeltmelere bakar ve sorunu eklemek için bir düzeltme eki ile birlikte sorunu açıklayan bir hata yayar :&&

error: passing value of type 'Int' to an inout parameter requires explicit '&'
foo(x)
    ^
    &

Bu örnekte, içinde tek bir tür hata vardı, ancak bu tanılama mimarisi kodda birden çok farklı tür hata da oluşturabilir. Biraz daha karmaşık bir örnek düşünün:

func foo(_: inout Int, bar: String) {}

var x: Int = 0
foo(x, "bar")

Bu kısıtlama sistemini çözerken tip denetleyicisi yine &ilk argümandaki eksiklikten kaynaklanan bir hatayı kaydedecektir foo. Ayrıca, eksik bağımsız değişken etiketi için bir hata kaydeder bar. Her iki arıza da kaydedildiğinde, kısıtlama sisteminin geri kalanı çözülür. Daha sonra tip denetleyicisi, bu kodu düzeltmek için ele alınması gereken iki sorun için hatalar (Fix-Its ile) üretir:

error: passing value of type 'Int' to an inout parameter requires explicit '&'
foo(x)
   ^
    &
error: missing argument label 'bar:' in call
foo(x, "bar")
      ^
       bar: 

Her özel hatanın kaydedilmesi ve ardından kalan kısıtlama sisteminin çözümüne devam edilmesi, bu arızaların giderilmesinin iyi yazılmış bir çözüm üreteceği anlamına gelir. Bu, tip denetleyicisinin, geliştiriciyi doğru koda yönlendiren, genellikle düzeltmeleri olan işlem yapılabilir tanılama üretmesine olanak tanır.

Geliştirilmiş Teşhis Örnekleri

Eksik etiket (ler)

Aşağıdaki geçersiz kodu göz önünde bulundurun:

func foo(answer: Int) -> String { return "a" }
func foo(answer: String) -> String { return "b" }

let _: [String] = [42].map { foo($0) }

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: argument labels '(_:)' do not match any available overloads`

Bu şimdi teşhis edildi:

error: missing argument label 'answer:' in call
let _: [String] = [42].map { foo($0) }
                                 ^
                                 answer:

Bağımsız Değişken-Parametre Dönüşüm Uyuşmazlığı

Aşağıdaki geçersiz kodu göz önünde bulundurun:

let x: [Int] = [1, 2, 3, 4]
let y: UInt = 4

_ = x.filter { ($0 + y)  > 42 }

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: binary operator '+' cannot be applied to operands of type 'Int' and 'UInt'`

Bu şimdi teşhis edildi:

error: cannot convert value of type 'UInt' to expected argument type 'Int'
_ = x.filter { ($0 + y)  > 42 }
                     ^
                     Int( )

Geçersiz İsteğe Bağlı Açma

Aşağıdaki geçersiz kodu göz önünde bulundurun:

struct S<T> {
  init(_: [T]) {}
}

var i = 42
_ = S<Int>([i!])

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: type of expression is ambiguous without more context

Bu şimdi teşhis edildi:

error: cannot force unwrap value of non-optional type 'Int'
_ = S<Int>([i!])
            ~^

Kayıp Üyeler

Aşağıdaki geçersiz kodu göz önünde bulundurun:

class A {}
class B : A {
  override init() {}
  func foo() -> A {
    return A() 
  }
}

struct S<T> {
  init(_ a: T...) {}
}

func bar<T>(_ t: T) {
  _ = S(B(), .foo(), A())
}

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: generic parameter ’T’ could not be inferred

Bu şimdi teşhis edildi:

error: type 'A' has no member 'foo'
    _ = S(B(), .foo(), A())
               ~^~~~~

Eksik Protokol Uygunluğu

Aşağıdaki geçersiz kodu göz önünde bulundurun:

protocol P {}

func foo<T: P>(_ x: T) -> T {
  return x
}

func bar<T>(x: T) -> T {
  return foo(x)
}

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: generic parameter 'T' could not be inferred

Bu şimdi teşhis edildi:

error: argument type 'T' does not conform to expected type 'P'
    return foo(x)
               ^

Koşullu Uygunluklar

Aşağıdaki geçersiz kodu göz önünde bulundurun:

extension BinaryInteger {
  var foo: Self {
    return self <= 1
      ? 1
      : (2...self).reduce(1, *)
  }
}

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: ambiguous reference to member '...'

Bu şimdi teşhis edildi:

error: referencing instance method 'reduce' on 'ClosedRange' requires that 'Self.Stride' conform to 'SignedInteger'
      : (2...self).reduce(1, *)
                   ^
Swift.ClosedRange:1:11: note: requirement from conditional conformance of 'ClosedRange<Self>' to 'Sequence'
extension ClosedRange : Sequence where Bound : Strideable, Bound.Stride : SignedInteger {
          ^

SwiftUI Örnekleri

Bağımsız Değişken-Parametre Dönüşüm Uyuşmazlığı

Aşağıdaki geçersiz SwiftUI kodunu göz önünde bulundurun:

import SwiftUI

struct Foo: View {
  var body: some View {
    ForEach(1...5) {
      Circle().rotation(.degrees($0))
    }
  }
}

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: Cannot convert value of type '(Double) -> RotatedShape<Circle>' to expected argument type '() -> _'

Bu şimdi teşhis edildi:

error: cannot convert value of type 'Int' to expected argument type 'Double'
        Circle().rotation(.degrees($0))
                                   ^
                                   Double( )

Kayıp Üyeler

Aşağıdaki geçersiz SwiftUI kodunu göz önünde bulundurun:

import SwiftUI

struct S: View {
  var body: some View {
    ZStack {
      Rectangle().frame(width: 220.0, height: 32.0)
                 .foregroundColor(.systemRed)

      HStack {
        Text("A")
        Spacer()
        Text("B")
      }.padding()
    }.scaledToFit()
  }
}

Önceden, bu tamamen ilgisiz bir problem olarak teşhis edilmişti:

error: 'Double' is not convertible to 'CGFloat?'
      Rectangle().frame(width: 220.0, height: 32.0)
                               ^~~~~

Yeni tanı şimdi doğru bir şekilde böyle bir rengin olmadığını gösteriyor systemRed:

error: type 'Color?' has no member 'systemRed'
                   .foregroundColor(.systemRed)
                                    ~^~~~~~~~~

Eksik argümanlar

Aşağıdaki geçersiz SwiftUI kodunu göz önünde bulundurun:

import SwiftUI

struct S: View {
  @State private var showDetail = false

  var body: some View {
    Button(action: {
      self.showDetail.toggle()
    }) {
     Image(systemName: "chevron.right.circle")
       .imageScale(.large)
       .rotationEffect(.degrees(showDetail ? 90 : 0))
       .scaleEffect(showDetail ? 1.5 : 1)
       .padding()
       .animation(.spring)
    }
  }
}

Önceden, bu aşağıdaki tanılama ile sonuçlanmıştı:

error: type of expression is ambiguous without more context

Bu şimdi teşhis edildi:

error: member 'spring' expects argument of type '(response: Double, dampingFraction: Double, blendDuration: Double)'
         .animation(.spring)
                     ^

Sonuç

Yeni teşhis altyapısı, eski yaklaşımın tüm eksikliklerinin üstesinden gelmek için tasarlanmıştır. Yapılandırılması, mevcut tanılama araçlarının geliştirilmesini / taşınmasını kolaylaştırmak ve yeni özellik uygulayıcıları tarafından yarasadan itibaren mükemmel tanılama sağlamak için kullanılmasını amaçlamaktadır. Şimdiye kadar yaptığımız tüm teşhislerde çok umut verici sonuçlar veriyor ve her geçen gün daha fazla iş yapmakta zorlanıyoruz.

Comments

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

GIPHY App Key not set. Please check settings

Yükleniyor…

0

Ne düşünüyorsun?

Bazı insanlar DevOps'un geliştiricilerin operasyonları devraldıkları ve kendileri yaptıkları anlamına geldiğini düşünüyor. Bunun bir kısmı doğru, bir kısmı da değil.

DevOps Nedir?

Yukarıdaki hata düzeltme testleri son derece faydalı olsa da, daha önce neden olmuş bir hataya tepki olarak yazılırlar ve bizi aynı gerilemeye karşı korurlar.

Swift | Dark Mode | 2020