Bagaimana menerapkan prinsip KERING saat menggunakan kata kunci 'menggunakan'?

23

Pertimbangkan metode ini:

public List<Employee> GetAllEmployees()
{
    using (Entities entities = new Entities())
    {
        return entities.Employees.ToList();
    }
}

public List<Job> GetAllJobs()
{
    using (Entities entities = new Entities())
    {
        return entities.Jobs.ToList();
    }
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    using (Entities entities = new Entities())
    {
        return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
    }
}

Penggunaan blok sama dan telah diulang 3 kali di sini (tentu saja, lebih dari 100 kali dalam aplikasi nyata). Bagaimana mungkin menerapkan prinsip DRY (Don't Repeat Yourself) untuk usingblok? Apakah itu dianggap sebagai pelanggaran prinsipal KERING sama sekali?

Pembaruan: Saya tidak berbicara tentang apa yang telah diterapkan di dalam usingblok. Apa yang sebenarnya saya maksud di sini, adalah using (Entities entities = new Entities()). Baris ini diulang 100 kali atau lebih.

Saeed Neamati
sumber
2
apakah ini C #? Jawaban atas pertanyaan Anda mungkin bergantung pada bahasa
David
Ya @ David, maaf saya tidak menyebutkan bahasa saya. Bagaimana itu bisa mempengaruhi jawabannya?
Saeed Neamati
beberapa bahasa dapat memiliki sintaks tertentu yang dapat membantu Anda memfaktorkan sedikit kode Anda. Saya tidak tahu C #, tetapi di Ruby, saya pikir Anda bisa menggunakan blok untuk memfaktorkan bagian penggunaan.
David
The menggunakan pernyataan benar-benar menyediakan C dukungan # bahasa untuk menerapkan prinsip KERING untuk membantu menghindari berulang coding sambil mengelola pembuangan sumber daya dengan pola desain Buang . Itu tidak berarti kita tidak dapat menemukan cara untuk membuat segalanya KERING! Secara pribadi, saya menganggap KERING sebagai proses rekursif.
John Tobler

Jawaban:

24

Satu ide adalah untuk membungkusnya dengan fungsi yang membutuhkan Func.

Sesuatu seperti ini

public K UsingT<T,K>(Func<T,K> f) where T:IDisposable,new()
{
    using (T t = new T())
    {
        return f(t);
    }
}

Maka kode Anda di atas menjadi

public List<Employee> GetAllEmployees()
{
    return UsingT<Entities,List<Employee>>(e=>e.Employees.ToList());
}

public List<Job> GetAllJobs()
{
    return UsingT<Entities,List<Job>>(e=>e.Jobs.ToList());
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    return UsingT<Entities,List<Task>>(e=>e.Tasks.Where(t => t.JobId == job.Id).ToList());
}

Saya membuat Entitiesparam tipe juga, karena saya berasumsi Anda memiliki lebih dari satu tipe yang Anda lakukan dengan ini. Jika tidak, Anda bisa menghapusnya dan cukup menggunakan param jenis untuk jenis kembali.

Sejujurnya meskipun kode semacam ini tidak membantu keterbacaan sama sekali. Dalam pengalaman saya, semakin banyak rekan kerja Jr yang mengalami kesulitan juga.

Perbarui Beberapa variasi tambahan tentang pembantu yang mungkin Anda pertimbangkan

//forget the Entities type param
public T UsingEntities<T>(Func<Entities,T> f)
{
    using (Entities e = new Entities())
    {
        return f(e);
    }
}
//forget the Entities type param, and return an IList
public IList<T> ListFromEntities<T>(Func<Entities,IEnumerable<T>> f)
{
    using (Entities e = new Entities())
    {
        return f(e).ToList();
    }
}
//doing the .ToList() forces the results to enumerate before `e` gets disposed.
Anak sungai
sumber
1
+1 Solusi yang bagus, meskipun tidak mengatasi masalah aktual (tidak termasuk dalam pertanyaan awal), yaitu banyaknya kejadian Entities.
back2dos
@ Doc Brown: Saya pikir saya mengalahkan Anda untuk itu dengan nama fungsi. Saya meninggalkan IEnumerablefungsi jika ada non- IEnumerableproperti T yang diinginkan pemanggil dikembalikan, tetapi Anda benar, itu akan sedikit membersihkannya. Mungkin memiliki pembantu untuk Single dan IEnumerablehasilnya akan bagus. Yang mengatakan, saya masih berpikir itu memperlambat pengakuan tentang apa yang dilakukan kode, terutama untuk seseorang yang tidak terbiasa menggunakan banyak obat generik dan lambda (mis. Rekan kerja Anda yang TIDAK pada SO :))
Brook
+1 Saya pikir pendekatan ini baik-baik saja. Dan keterbacaan dapat ditingkatkan. Misalnya, masukkan "ToList" ke dalam WithEntities, gunakan Func<T,IEnumerable<K>>sebagai ganti Func<T,K>, dan beri "WithEntities" nama yang lebih baik (seperti SelectEntities). Dan saya tidak berpikir "Entitas" perlu menjadi parameter umum di sini.
Doc Brown
1
Untuk membuat ini bekerja, kendala harus where T : IDisposable, new(), seperti yang usingdiperlukan IDisposableuntuk bekerja.
Anthony Pegram
1
@Anthony Pegram: Diperbaiki, terima kasih! (itulah yang saya dapatkan untuk coding C # di jendela browser)
Brook
23

Bagi saya ini akan seperti mengkhawatirkan tentang mencari koleksi yang sama beberapa kali: itu hanya sesuatu yang perlu Anda lakukan. Setiap upaya untuk abstrak lebih lanjut akan membuat kode tersebut jauh lebih mudah dibaca.

Ben Hughes
sumber
Analogi hebat @Ben. +1
Saeed Neamati
3
Saya tidak setuju, maaf. Baca komentar OP tentang ruang lingkup transaksi dan pikirkan apa yang harus Anda lakukan ketika Anda telah menulis 500 kali kode semacam itu, dan kemudian perhatikan bahwa Anda harus mengubah hal yang sama 500 kali. Pengulangan kode semacam ini mungkin ok hanya jika Anda memiliki <10 fungsi tersebut.
Doc Brown
Oh, dan jangan lupa, jika Anda untuk-masing-masing koleksi yang sama lebih dari 10 kali dengan cara yang sangat mirip, dengan kode yang tampak serupa di dalam untuk-masing-masing Anda, Anda harus memikirkan beberapa generalisasi.
Doc Brown
1
Bagi saya itu hanya seperti Anda membuat tiga liner menjadi oneliner ... Anda masih mengulangi sendiri.
Jim Wolff
Ini tergantung konteks, jika foreachkoleksi yang sangat besar atau logika dalam foreachloop memakan waktu misalnya. Sebuah motto yang saya adopsi: Jangan terobsesi tetapi selalu perhatikan pendekatan Anda
Coops
9

Sepertinya Anda mengacaukan prinsip "Once and Only Once" dengan prinsip KERING. Prinsip KERING menyatakan:

Setiap pengetahuan harus memiliki representasi tunggal, tidak ambigu, otoritatif dalam suatu sistem.

Namun prinsip Once dan Only Once sedikit berbeda.

Prinsip [KERING] mirip dengan OnceAndOnlyOnce, tetapi dengan tujuan yang berbeda. Dengan OnceAndOnlyOnce, Anda dianjurkan untuk melakukan refactor untuk menghilangkan duplikasi kode dan fungsionalitas. Dengan KERING, Anda mencoba mengidentifikasi sumber tunggal dan pasti dari setiap bagian pengetahuan yang digunakan dalam sistem Anda, dan kemudian menggunakan sumber itu untuk menghasilkan contoh-contoh yang berlaku dari pengetahuan itu (kode, dokumentasi, tes, dll).

Prinsip KERING biasanya digunakan dalam konteks logika aktual, tidak terlalu berlebihan menggunakan pernyataan:

Menjaga struktur program KERING lebih sulit, dan nilainya lebih rendah. Aturan bisnis, pernyataan if, rumus matematika, dan metadata yang seharusnya hanya muncul di satu tempat. Hal-hal WET - halaman HTML, data uji yang berlebihan, koma dan pembatas {} - semuanya mudah untuk diabaikan, jadi KERING mereka kurang penting.

Sumber

Phil
sumber
7

Saya gagal melihat penggunaan di usingsini:

Bagaimana tentang:

public List<Employee> GetAllEmployees() {
    return (new Entities()).Employees.ToList();
}
public List<Job> GetAllJobs() {
    return (new Entities()).Jobs.ToList();
}
public List<Task> GetAllTasksOfTheJob(Job job) {
    return (new Entities()).Tasks.Where(t => t.JobId == job.Id).ToList();
}

Atau bahkan lebih baik, karena saya pikir Anda tidak perlu membuat objek baru setiap saat.

private Entities entities = new Entities();//not sure C# allows for that kind of initialization, but you can do it in the constructor if needed

public List<Employee> GetAllEmployees() {
    return entities.Employees.ToList();
}
public List<Job> GetAllJobs() {
    return entities.Jobs.ToList();
}
public List<Task> GetAllTasksOfTheJob(Job job) {
    return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
}

Adapun untuk melanggar KERING: KERING tidak berlaku pada tingkat ini. Sebenarnya tidak ada prinsip yang benar-benar berlaku, kecuali keterbacaan. Mencoba menerapkan KERING pada tingkat itu benar-benar hanya mikro-optimasi arsitektur, yang seperti semua mikro-optimasi hanya sepeda-shedding dan tidak mendapatkan masalah diselesaikan, tetapi bahkan risiko untuk memperkenalkan yang baru.
Dari pengalaman saya sendiri, saya tahu bahwa jika Anda mencoba mengurangi redundansi kode pada level itu, Anda membuat dampak negatif pada kualitas kode, dengan mengaburkan apa yang benar-benar jelas dan sederhana.

Sunting:
Oke. Jadi masalahnya sebenarnya bukan statemen using, masalahnya adalah ketergantungan pada objek yang Anda buat setiap waktu. Saya sarankan menyuntikkan konstruktor:

private delegate Entities query();//this should be injected from the outside (upon construction for example)
public List<Employee> GetAllEmployees() {
    using (var entities = query()) {//AFAIK C# can infer the type here
        return entities.Employees.ToList();
    }
}
//... and so on
back2dos
sumber
2
Tapi @ back2do, ada banyak tempat di mana kita menggunakan using (CustomTransaction transaction = new CustomTransaction())blok kode dalam kode kita untuk mendefinisikan ruang lingkup transaksi. Itu tidak dapat digabungkan menjadi satu objek dan di setiap tempat Anda ingin menggunakan transaksi, Anda harus menulis blokir. Sekarang bagaimana jika Anda ingin mengubah jenis transaksi dari CustomTransactionmenjadi BuiltInTransactiondalam lebih dari 500 metode? Bagi saya ini merupakan tugas yang berulang dan contoh pelanggaran prinsipal KERING.
Saeed Neamati
3
Setelah "menggunakan" di sini menutup konteks data sehingga pemuatan malas tidak dimungkinkan di luar metode ini.
Steven Striga
@ Saeed: Saat itulah Anda melihat injeksi ketergantungan. Tetapi itu tampaknya sangat berbeda dari kasus seperti yang dinyatakan dalam pertanyaan.
CVn
@ Saeed: Posting diperbarui.
back2dos
@WeekendWarrior Apakah using(dalam konteks ini) masih merupakan "sintaksis yang nyaman"? Mengapa sangat menyenangkan untuk menggunakan =)
Coops
4

Tidak hanya menggunakan kode duplikat (dengan cara itu adalah kode duplikat dan benar-benar membandingkan dengan coba..catch .. pernyataan terakhir) tetapi toList juga. Saya akan memperbaiki kode Anda seperti ini:

 public List<T> GetAll(Func<Entities, IEnumerable<T>> getter) {
    using (Entities entities = new Entities())
    {
        return getter().ToList();
    }
 }

public List<Employee> GetAllEmployees()
{
    return GetAll(e => e.Employees);
}

public List<Job> GetAllJobs()
{
    return GetAll(e => e.Jobs);
}

public List<Task> GetAllTasksOfTheJob(Job job)
{
    return GetAll(e => e.Tasks.Where(t => t.JobId == job.Id));
}
Kohen
sumber
3

Karena tidak ada logika bisnis apa pun di sini kecuali yang terakhir. Ini tidak benar-benar KERING, menurut saya.

Yang terakhir tidak memiliki KERING di blok menggunakan tapi saya kira klausa di mana harus berubah di mana pun itu digunakan.

Ini adalah pekerjaan khas untuk pembuat kode. Tulis dan tutup pembuat kode dan biarkan menghasilkan untuk setiap jenis.

arunmur
sumber
Tidak ada @arunmur. Ada kesalahpahaman di sini. Dengan KERING, maksudku using (Entities entities = new Entities())blok. Maksud saya, baris kode ini diulang 100 kali dan semakin berulang.
Saeed Neamati
KERING terutama berasal dari kenyataan bahwa Anda perlu menulis kasus uji yang berkali-kali dan bug di satu tempat berarti Anda harus memperbaikinya di 100 tempat. Penggunaan (Entitas ...) adalah kode yang terlalu sederhana untuk dipecah. Tidak perlu dipecah atau diletakkan di kelas lain. Jika Anda masih bersikeras untuk menyederhanakannya. Saya akan menyarankan fungsi panggilan balik Yeild dari ruby.
arunmur
1
@arnumur - menggunakan tidak selalu mudah untuk dipatahkan. Saya sering memiliki sedikit logika yang menentukan opsi mana yang digunakan pada konteks data. Sangat mungkin bahwa string koneksi yang salah dapat diteruskan.
Morgan Herlocker
1
@ironcode - Dalam situasi itu, masuk akal untuk mendorongnya ke suatu fungsi. Tetapi dalam contoh-contoh ini cukup sulit untuk dihancurkan. Bahkan jika itu melanggar perbaikan sering dalam definisi kelas Entitas itu sendiri, yang harus ditutup dengan test case terpisah.
arunmur
+1 @arunmur - Saya setuju. Saya biasanya melakukannya sendiri. Saya menulis tes untuk fungsi itu, tetapi sepertinya agak berlebihan untuk menulis tes untuk pernyataan penggunaan Anda.
Morgan Herlocker
2

Karena Anda membuat dan menghancurkan objek sekali pakai yang sama berulang-ulang, kelas Anda sendiri merupakan kandidat yang baik untuk menerapkan pola IDisposable.

class ThisClass : IDisposable
{
    protected virtual Entities Context { get; set; }

    protected virtual void Dispose( bool disposing )
    {
        if ( disposing && Context != null )
            Context.Dispose();
    }

    public void Dispose()
    {
        Dispose( true );
    }

    public ThisClass()
    {
        Context = new Entities();
    }

    public List<Employee> GetAllEmployees()
    {
        return Context.Employees.ToList();
    }

    public List<Job> GetAllJobs()
    {
        return Context.Jobs.ToList();
    }

    public List<Task> GetAllTasksOfTheJob(Job job)
    {
        return Context.Tasks.Where(t => t.JobId == job.Id).ToList();
    }
}

Ini membuat Anda hanya perlu "menggunakan" saat membuat instance kelas Anda. Jika Anda tidak ingin kelas bertanggung jawab untuk membuang objek, maka Anda bisa membuat metode menerima ketergantungan sebagai argumen:

public static List<Employee> GetAllEmployees( Entities entities )
{
    return entities.Employees.ToList();
}

public static List<Job> GetAllJobs( Entities entities )
{
    return entities.Jobs.ToList();
}

public static List<Task> GetAllTasksOfTheJob( Entities entities, Job job )
{
    return entities.Tasks.Where(t => t.JobId == job.Id).ToList();
}
Sean
sumber
1

Sepotong sihir favoritku yang tak bisa dipercaya!

public class Blah
{
  IEnumerable<T> Wrap(Func<Entities, IEnumerable<T>> act)
  {
    using(var entities = new Entities()) { return act(entities); }
  }

  public List<Employee> GetAllEmployees()
  {
    return Wrap(e => e.Employees.ToList());
  }

  public List<Job> GetAllJobs()
  {
    return Wrap(e => e.Jobs.ToList());
  }

  public List<Task> GetAllTasksOfTheJob(Job job)
  {
    return Wrap(e => e.Tasks.Where(x ....).ToList());
  }
}

Wrapada hanya untuk abstrak itu atau sihir apa pun yang Anda butuhkan. Saya tidak yakin saya akan merekomendasikan ini sepanjang waktu tetapi mungkin untuk digunakan. Gagasan "yang lebih baik" adalah dengan menggunakan wadah DI, seperti StructureMap, dan hanya lingkup kelas Entitas ke konteks permintaan, menyuntikkannya ke dalam pengontrol, dan kemudian membiarkannya mengurus siklus hidup tanpa perlu pengontrol Anda.

Travis
sumber
Tipe paramaters Anda untuk Func <IEnumerable <T>, Entities> berada di urutan yang salah. (lihat jawaban saya yang pada dasarnya memiliki hal yang sama)
Brook
Yah, cukup mudah untuk dikoreksi. Saya tidak pernah ingat urutan yang benar. Saya menggunakan Funccukup saya harus.
Travis