Dùng Dagger 2 DI trong Android

Cài đặt

Integrate thư viện vào project Android

Dagger sẽ tự động generate code khi bạn build project, tuy nhiên, mặc định Android Studio không thừa nhận việc generate ra những đoạn code như vậy, và chúng ta sẽ add thêm plugin sau vào root build.gradle để giai quyết vấn đề đó.

  dependencies {
     // other classpath definitions here
     classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
 }

Thêm đoạn script sau vào app/build.gradle

dependencies {
    // apt command comes from the android-apt plugin
    apt 'com.google.dagger:dagger-compiler:2.7'
    compile 'com.google.dagger:dagger:2.7'
    provided 'javax.annotation:jsr250-api:1.0'
}

Dagger đang được hỗ trợ rất tốt nên các version sẽ được cập nhật thường xuyên, do đó các bạn cần chú ý đến version của các dependencies cũng như plugin sẽ được sử dụng.

Implementation

Tư tưởng của Dependency Injection là làm cho các module, class giao tiếp với nhau thông qua một abstract và abstract này sẽ được inject vào high-level class thông qua một Injector, hay nói cách các high-level class sẽ giao tiếp với low-level class thông qua một abstract mà không cần biết nó được khởi tạo như thế nào. Dagger rõ ràng được xây dựng dựa trên tư tưởng đó.

Các thành phần chính của Dagger

  1. Service: hay còn gọi là Module, là nơi cung cấp các abstract để inject vào high-level class.
  2. Client: là nơi sử dụng các Service được inject vào thông qua một Injector.
  3. DI container : thường được biết đến như là Object Graph, nơi cung cấp các Dependency (Service) sẽ được inject vào Client.

Annotation

Dagger sử dụng các annotation để implement nhằm tạo sự đơn gian cũng như tường minh của code.

  • @Scope: annotation định nghĩa vòng đời tồn tại của Object graph, nó thật sự hữu ích trong việc quản lý vòng đời của các Service sẽ được cung cấp cho Client. Chúng ta có thể define các loại Scope khác nhau như một annotation mới và sử dụng trong từng trường hợp cụ thể. Dưới đây là một ví dụ:
@Scope
@Documented
@Retention(value=RetentionPolicy.RUNTIME)
public @interface ActivityScope
{
}
  • @Module: annotation định nghĩa một Module, nơi sẽ cung cấp các service sẽ được inject vào Client.
  • @Provide: annotation được sử dụng trong một Module, định nghĩa các Service sẽ được cung cấp.
  • @Component: annotation định nghĩa một Component, là nơi sẽ expose ra toàn bộ các Service mà bạn dự định sẽ cung cấp cho Client.
  • @Inject: annotation được sử dụng ở Client, thông báo rằng service này sẽ được inject vào Client.
  • @Singleton: annotation sử dụng ở Module, đánh dấu Service được cung cấp dưới dạng một Singleton object.

Implementation

Chúng ta sẽ implement một Module, dùng để cung cấp các service giúp thao tác với API, parse dữ liệu trả về … Ta sẽ hình dung các Service cần cung cấp đến client và các Service cần để làm dependency lẫn nhau.

@Module
public class NetModule {

    private static final String BASE_ENPOINT = "http://dummy.endpoint";

    // Dagger will only look for methods annotated with @Provides
    @Provides
    @Singleton
    // Application reference must come from AppModule.class
    SharedPreferences providesSharedPreferences(Application application) {
        return PreferenceManager.getDefaultSharedPreferences(application);
    }

    @Provides
    @Singleton
    Cache provideOkHttpCache(Application application) {
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        Cache cache = new Cache(application.getCacheDir(), cacheSize);
        return cache;
    }

   @Provides
   @Singleton
   Gson provideGson() {
       GsonBuilder gsonBuilder = new GsonBuilder();
       gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
       return gsonBuilder.create();
   }

   @Provides
   @Singleton
   OkHttpClient provideOkHttpClient(Cache cache) {
      OkHttpClient client = new OkHttpClient();
      client.setCache(cache);
      return client;
   }

   @Provides
   @Singleton
   Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
      Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(BASE_ENPOINT)
                .client(okHttpClient)
                .build();
        return retrofit;
    }
}

  • Ở ví dụ trên, ta cần một annotation @Module để khai báo một module mới. Trong module này, ta sẽ sử dụng @Provide để định nghĩa các Service sẽ được cung cấp và @Singleton để đánh dấu rằng các Service này sẽ được khởi tạo một lần. Rõ ràng, chúng ta thấy có những Service được định nghĩa để cung cấp cho Client (ví dụ như Retrofit), cũng có những service được định nghĩa để làm dependency cho các service khác (GsonOkHttpClientCache…)

Chúng ta tiếp tục implement Component, là nơi sẽ expose ra các Service sẽ được client sử dụng.

@Singleton
@Component(modules={NetModule.class})
public interface NetComponent {
   Retrofit retrofit();
   Gson gson();
}

Component NetComponent sẽ expose cho client 2 service là Retrofit và Gson, khi sử dụng ở client, chúng ta sẽ inject nó vào bằng annotation @Inject, sẽ xem xét ở những ví dụ sau.

NetComponent sẽ provide các Service được sử dụng trong toàn app, do đó, scope của nó sẽ và Application hay Graph Object được tạo ra và tồn tại theo vòng đời của Application. Chúng ta tiến hành implement nó trong Application class.

public class MyApp extends Application {

    private NetComponent mNetComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        mNetComponent = DaggerNetComponent.builder()
                // list of modules that are part of this component need to be created here too
                .netModule(new NetModule())
                .build();
    }

    public NetComponent getNetComponent() {
       return mNetComponent;
    }
}

Chúng ta sẽ tiếp tục implement một Component khác, có dependency là NetComponent đã được định nghĩa ở trên. Component này sẽ được sử dụng như một Injector và cung cấp các Service cho client.

@ActivityScope
@Component(dependencies = NetComponent.class, modules= NetModule.class)
public interface AppComponent {
   void inject(MyActivity activity);
}

Annotation @ActivityScope được sử dụng để hạn chế scope của Object Graph, chỉ tồn tại trong vòng đời của một Activity. Ta sẽ tiến hành implement Client.

public class MyActivity extends Activity {
  @Inject Retrofit retrofit;
  @Inject Gson gson;

  @Overide
  public void onCreate(Bundle savedInstance) {
        DaggerAppComponent.builder()
                .netComponent(((MyApp)getApplication()).getNetComponent())
                .build().inject(this);

    }
}

Dagger sẽ tự động tìm kiếm trong Object graph của mình và inject vào Client các Service được đánh dấu bởi @Inject và chúng ta có thể sử dụng chúng mà không cần quan tâm đến cách khởi tạo như thế nào nhé, đó cũng chính là tư tưởng của DI.

Kết luận

Dagger 2 còn cung cấp cho chúng ta rất nhiều tính năng hay ho khác, nhưng trong giới hạn của bài viết, tôi chỉ đề cập đến những tính năng nổi bật nhất mà chúng ta có thể implement ngay trong ứng dụng của mình. Hy vọng rằng các bạn có thể sử dụng Dagger để xây dựng cho mình một ứng dụng “sạch” và “đẹp”. Good luck!!

Dependency Inversion, Inversion of Control and Dependency Injection

Chào mọi người, chắc hẳn trong giới lập trình phần mềm của chúng ta, ai cũng ít nhất một lần nghe đến các khái niệm như SOLID, OOP Design, Dependency Inversion hay IoC … Tuy nhiên không hẳn ai cũng hiểu rõ và thực hành thành công. Theo nhận thức của tôi, rất nhiều kỹ sư phần mềm đã đi làm thực tế được 1-2 năm, thậm chí là 3-4 năm vẫn còn mơ hồ về các khái niệm này và dĩ nhiên ứng dụng nó trong lập trình sẽ cực kỳ khó. Bản thân tôi, trong khả năng của mình cũng chưa dám chắc đã hiểu rõ về tất cả các khái niệm đó. Tuy nhiên, trong giới hạn bài viết này, tôi sẽ chia sẻ một số hiểu biết dựa trên lý thuyết và kinh nghiệm làm việc của bản thân để làm rõ thêm những khái niệm nêu trên mà theo tôi là vô cùng quan trọng và cần thiết đối với một lập trình viên.

Dependency Inversion

  • Dám chắc rằng chúng ta đều biết đến một khái niệm vô cùng quan trọng trong lập trình, đó là OOP Design – Thiết kế hướng đối tượng. Mọi dự án phần mềm làm ra không những đòi hỏi tính đúng đắn về business, về giao diện người dùng, về chức năng … mà còn đòi hỏi sự dễ dàng trong việc mở rộng và maintain. Thiết kế hướng đối tượng ra đời với mục đích đó. Trải qua nhiều dự án phần mềm, người ta đã rút ra được 5 nguyên tắc cơ bản trong Thiết kế hướng đối tượng đó là :
    • Single responsibility principle
    • Open/closed principle
    • Liskov substitution principle
    • Interface segregation principle
    • Dependency inversion principle

    Trong bài viết hôm nay, tôi sẽ làm rõ khái niệm vô cùng quan trọng trong 5 nguyên tắc thiết kế hướng đối tượng, đó là Dependency Inversion.

  • Dependency Inversion phát biểu như sau :
    • Các module, class cấp cao (high-level) không nên phụ thuộc vào module, class cấp thấp hơn (low-level) mà nên phụ thuộc (giao tiếp) với nhau thông qua một abstraction (Interface).
    • Abtraction không nên phụ thuộc vào chi tiết. Chi tiết nên phụ thuộc vào abtraction.

    Khó hiểu quá. Chắc hẳn đọc khái niệm bao giờ cũng vậy, trừu tượng và khó hiểu, đơn giản vì nó chỉ là một nguyên lý, một phát biểu ở mức khái niệm, muốn hiểu rõ hơn tôi sẽ dẫn bạn đến một implementation.

    Giả sử tôi có :

    • Một class Circle (đây là một class low-level) có public cho tôi 2 method : getPerimeter() và getArea() để tính chu vi và diện tích của hình tròn.
    • Một class ShapeManager (đây là một class high-level) sẽ public 2 method : calculatePerimeter() và calculateArea();

    Tôi muốn in ra màn hình diện tích của hình tròn có bán kính cố định nào đó, tôi sẽ implement như sau.

    public class ShapeManager {

        private Circle circle;

        public ShapeManager() {
        }

        public void setCircle(Circle circle){
            this.circle = circle;
        }

        public float calculatePerimeter() {
            return circle.getPerimeter();
        }

        public float calculateArea() {
            return circle.getArea();
        }

    }

Và ở hàm main, tôi sẽ chỉ việc gọi như sau:

public static void main(String[] args) {
		ShapeManager manager = new ShapeManager();
        manager.setCircle(new Circle(5));
		System.out.println("Circle with perimeter and area: "
				+ manager.calculatePerimeter() + ":" + manager.calculateArea());

	}

Code thoạt nhìn không có vấn đề gì cả và vẫn cho ra kết quả đúng. Thế nhưng, nếu tôi muốn mở rộng class ShapeManager có thể tính được nhiều hình khác nhau : hình vuông, hình chữ nhật, hình thoi … thì tôi phải tạo thêm nhiều đối tượng trong class ShapeManager. Càng lúc, class này sẽ phình to ra tới mức tôi không thể hình dung ra được. Rõ ràng, chúng ta đang thấy class high-level ở đây là ShapeManager đang phụ thuộc vào class low-level. Dependency Inversion muốn các module này không nên phụ thuộc vào nhau và giao tiếp với nhau thông qua một abstraction (Interface). Bởi lẽ đó, Inversion of Control ra đời để làm nhiệm vụ đó.

Inversion of Control (IoC)

  • IoC là một design pattern để hiện thực hóa hay implement nguyên lý thiết kế Dependency Inversion nêu trên. Dĩ nhiên, nó sẽ tuân thủ đầy đủ những nguyên tắc là Dependency Inversion phát biểu. Ngay cả cái tên của nó cũng gợi cho ta đôi chút về ý nghĩa của pattern này – sự đảo ngược điều khiển mà với Dependency Injection, bạn sẽ hiểu rõ hơn về khái niệm này.
  • IoC sẽ không quan tâm đến việc Service được khởi tạo như thế nào mà chỉ quan tâm đến những gì mà nó cung cấp thông qua một abstraction. Điều này tuân thủ chặt chẽ nguyên lý của Dependency Inversion nêu trên, tức là các module high-level chỉ phụ thuộc hay giao tiếp với các module low-level thông qua một abstraction, và chính module high-level sẽ không cần biết module low-level sẽ được khởi tạo như thế nào mà chỉ cần biết những gì nó cung cấp. Phần này sẽ được nói chi tiết hơn ở Dependency Injection ở phần sau.
  • IoC sẽ có 1 Container để chứa các concretion implementation của các abstraction dùng để kết nối các module với nhau trong một object graph. Khó hiểu quá nhỉ? Hiểu nôm na nó giống như nơi lưu trữ các implementation của các abtraction mà bạn muốn truyền vào high-level module. Khi nào high-level module cần dùng, nó chỉ việc tìm trong Container với instance tương ứng và inject vào high-level module. Bởi vậy mà high-level module không thể biết Service(low-level module) mình dùng được tạo nên ở đâu là vậy đó.
  • Có nhiều cách để implement IoC như : Service LocatorEvent hay Dependency Injection … và mỗi loại đều có một ưu nhược điểm riêng mà tùy trường hợp sẽ được sử dụng cho phù hợp.

    Trở lại với ví dụ ở đầu bài, trong trường hợp muốn mở rộng class ShapeManager, tôi muốn hỗ trợ thêm các hình khác như hình vuông, hình chữ nhật, tôi sẽ tiến hành implement nó theo IoC.

  • Tạo một interface Shape. Interface này sẽ làm nhiệm vụ kết nối ShapeManager (high-level module) với các implementation của Shape (low-level module) hay nói cách khác ShapeManager chỉ phụ thuộc vào interface Shape mà ko cần quan tâm nó được khởi tạo ở đâu và bằng cách nào.
    public interface Shape {

        float getPerimeter();

        float getArea();

    }
  • Tạo các implementation của Shape. Đây chính là các Service(low-level module) cần dùng trong high-level module.
    public class Circle implements Shape {

        private static final float PI = 3.1415f;
        private int radius;

        public Circle(int radius) {
            this.radius = radius;
        }

        @Override
        public float getPerimeter() {
            return radius * 2 * PI;
        }

        @Override
        public float getArea() {
            return (float) (Math.pow(radius, 2) * PI);
        }

    }
    public class Square implements Shape {

        private int size;

        public Square(int size) {
            this.size = size;
        }

        @Override
        public float getPerimeter() {
            return size * 4;
        }

        @Override
        public float getArea() {
            return (float) Math.pow(size, 2);
        }

    }
  • Modify class ShapeManager.
    public class ShapeManager {

         private Shape shape;

         public void setShape(Shape shape) {
             this.shape = shape;
         }

         public float calculatePerimeter() {
             return this.shape.getPerimeter();
         }

         public float calculateArea() {
             return this.shape.getArea();
         }

    }
  • Thử gọi nó nào.
    public static void main(String[] args) {
            Shape circle = new Circle(5);
            Shape square = new Square(5);
            Shape rectangle = new Rectangle(4, 6);

            ShapeManager manager = new ShapeManager();
            manager.setShape(circle);
            System.out.println("Circle with perimeter and area: "
                    + manager.calculatePerimeter() + ":"
                    + manager.calculateArea());

            manager.setShape(square);
            System.out.println("Square with perimeter and area: "
                    + manager.calculatePerimeter() + ":"
                    + manager.calculateArea());

            manager.setShape(rectangle);
            System.out.println("Rectangle with perimeter and area: "
                    + manager.calculatePerimeter() + ":"
                    + manager.calculateArea());

        }
  • Rõ ràng từ ví dụ trên, ta thấy rõ : Nếu trường hợp cần mở rộng thêm các hình khác, ta chỉ cần tạo thêm class implement Shape mà không cần modify ShapeManager().
  • Nếu việc sử dụng các low-level module (trong ví dụ trên là các module Circle, Square …) diễn ra ở nhiều nơi thì việc phải khởi tạo nó sẽ mất rất nhiều thời gian và khó khăn cho việc maintain sau này, giải pháp được sử dụng ở đây là IoC Container.

Dependency Injection (DI)

  • Dependency Injection là một trong những pattern để implement Dependency Inversion, nó là một trong những subtype của IoC.
  • Nguyên tắc cơ bản của DI là làm cho high-level module phụ thuộc vào low-level module thông qua injector, hay nói cách khác, muốn tạo instance high-level module, ta phải tạo instance của low-level module và inject nó vào high-level module thông qua injector. Injector ở đây có thể là constructor, setter hay interface.
  • Nguyên tắc trên có vẻ mâu thuẫn vơi DIP (Dependency Inversion Principle), tuy nhiên nếu xem xét kỹ thì không hòan toàn vậy. Nguyên tắc của DI khác ở chỗ nó sẽ tạo ra sự phụ thuộc của high-level module và low-level module thông qua abstraction chứ ko phải một cách trực tiếp. Như vậy, high-level module sẽ sử dụng Service (low-level module abstraction) thông qua injector mà không quan tâm đến việc khởi tạo của nó. Thật khó hiểu, chính bản thân mình sau rất nhiều lần research vẫn còn chút mơ hồ về vấn đề này. Hãy đến với ví dụ sau.
    public class ShapeManager {

         private Shape shape;

         public ShapeManager(){
             this.shape = new Circle();
         }

         public float calculatePerimeter() {
             return this.shape.getPerimeter();
         }

         public float calculateArea() {
             return this.shape.getArea();
         }

    }
  • Rõ ràng ở ví dụ trên, biến shape là bất biến vì được khởi tạo ngay trong constructor. Điều này là không sai nhưng rất khó để mở rông. Module ShapeManager không phụ thuộc vào bất kỳ Service nào và việc tạo instance của nó cũng độc lập với các Service (low-level abstraction) khác, điều đó đi ngược lại với nguyên tắc của DI. Trong trường hợp này, ta sẽ inject Service vào high-level module thông qua injector.
    • Modify hàm dựng.
        public ShapeManager(Shape shape){
             this.shape = shape;
        }
+ Sử dụng setter
        public void setShape(Shape shape){
             this.shape = shape;
        }
+ Sử dụng interface.
        public interface ShapeSetter{
             void setShape(Shape shape);
        }
        public class ShapeManager implement ShapeSetter {
             private Shape shape;

             @Override
             public void setShape(Shape shape){
                 this.shape = shape;
             }

             public float calculatePerimeter() {
                 return this.shape.getPerimeter();
             }

             public float calculateArea() {
                 return this.shape.getArea();
             }
        }
  • Để inject Service vào high-level module, ta có thể inject manual hoặc dùng DI Container, các framework hỗ trợ rất tốt việc này.
  • DI được sử dụng trong hầu hết các ngôn ngữ hướng đối tượng và có rất nhiều framework hỗ trợ cho việc implement DI. Có thể kể đến Ninject trong C#, Spring trong Java, Dagger trong Android, Laravel trong PHP …
  • DI cũng như hầu hết các pattern khác đều có ưu nhược điểm riêng biệt, tùy từng trường hợp mà ta sẽ xác định dùng nó một cách phù hợp.
    • Ưu điểm : Giảm sự kết dính giữa các module với nhau, code trở nên đẹp và dễ dàng trong bảo trì, dễ dàng viết Unit Test …
    • Nhược điểm: Khó debug, có thể giảm performance vì sẽ tạo các instance ngay ở runtime (DI Container) …

Kết luận

Dependency InversionInversion of Control và Dependency Injection là các khái niệm mà mọi lập trình viên cần biết và sử dụng thuần thục. Trong khuôn khổ bài viết này, tôi chỉ chia sẻ những kiến thức và kinh nghiệm bản thân trong quá trình làm việc, có thể không tránh được những thiếu sót. Vì thế rất mong nhận được mọi góp ý từ các bạn

Hãy đưa cho tôi những người lười nhất mà tiền có thể mua được

Trong bài viết này Jeff Atwood có nói đến một kiểu “lười biếng thông minh”, thực chất là cách quản lý thời gian một cách hiệu quả hơn trong công việc. Trong thời đại bùng nổ thông tin và các phương thức giao tiếp như hiện nay thì bạn rất dễ sa đà vào các hoạt động như: chat chit, facebook, check email, news… dẫn đến mất tập trung trong công việc hiện tại. Khi làm công việc nào đó cần sự tập trung thì bạn nên tắt hết những thứ gây sao nhãng này đi nhé! 🙂

Khi làm việc thì bạn nên tắt hết các ứng dụng để được tập trung nhất có thể.Khi làm việc thì bạn nên tắt hết các ứng dụng để được tập trung nhất có thể.


Omar Shahine gần đây đã đăng một bài viết truyền cảm hứng ca ngợi sự lười biếng:

Cứ vài phút lại có một email đến và desktop alert kèm theo âm thanh khiến cho tôi rất dễ bị mất tập trung vào công việc hiện tại của mình và phải ngó vào phần inbox của hộp thư. Trong khi tôi rất thích chức năng này của Outlook khi nó mới ra đời, nhưng hiện nay nó đã trở thành gót chân Asin trong năng suất công việc của mình.

Nếu bạn muốn hoàn thành công việc, bạn phải học cách đánh giá đúng sự lười biếng là một nét tính cách tích cực trong thực tế. Và tôi xin đưa vấn đề này lên một mức cao hơn: bạn cũng nên tắt luôn phần thông báo mỗi khi có nội dung chat mới.

Có nhiều chi tiết về điều này trong hai bài viết của Ole Eichhorn là Tyranny of EmailTyranny Revisited:

Bất cứ khi nào bạn đang làm việc gì đó mà không yêu cầu sự tập trung, bởi tất cả ý nghĩa của nó, thì hãy chạy ứng dụng email client của bạn, chạy chương trình chat IM, có phần thông báo (notification) được bật lên, nhận các cuộc điện thoại… Nhưng khi bạn thực sự cần phải hoàn thành công việc, hãy tắt hết mọi thứ. Hãy cô lập chính bản thân bạn.

Trong một số trường hợp thì chính sự lười biếng đó có thể giúp ích cho công việc của bạn, nếu bạn tuân theo những điều sau:

1. Lựa chọn những việc không nên làm

Thế giới ngày nay là một sự kết hợp bùng nổ của rất nhiều hướng tiếp cận có thể được. Và tỉ lệ nhiễu ngày càng tăng lên. Việc lựa chọn điều gì không nên làm là rất quan trọng– và người ta có thể cho rằng điều đó còn quan trọng hơn– việc lựa chọn công việc nào để làm. Đây là sự lười biếng mang lại hiệu quả: tại sao lại phải dành ra 5 ngày để làm công việc nghiên cứu kỹ càng trên 10 giải pháp khác nhau khi mà bạn có thể nhanh chóng loại đi 8 trong số đó dựa trên một số tiêu chuẩn chính? Hãy hướng tới mục tiêu chính. Hãy trau dồi kỹ năng bỏ đi những hướng tiếp cận không phù hợp nhanh nhất bạn có thể. Việc download phần code của một vài module có sẵn về xài thì nhanh hơn nhiều so với việc tự mình ngồi viết ra nó.

2. Cân bằng giữa giao tiếp và cách ly

Mỗi ngày chúng ta luôn luôn phải giao tiếp trong các hoạt động thông thường– thông qua điện thoại, chat, email, mạng xã hội… Cái giá cho tất cả những hoạt động giao tiếp thường xuyên này đang ngày càng tăng lên vì nó làm ngắt quãng công việc của bạn. Trong một số lĩnh vực, giống như quản lý chẳng hạn, thì sự gián đoạn là dấu hiệu của một việc gì đó được hoàn thành. Nhưng nó lại là một yếu tố có hại trong phát triển phần mềm. Nếu chúng ta không thể có trạng thái hoàn toàn tập trung trong công việc, thì rất khó để chúng ta có thể làm việc hiệu quả được, vì vậy việc giao tiếp phải được quản lý cẩn trọng và đôi khi nên trì hoãn lại.

3. Con người không thể nâng công suất lên như máy móc được

Những nhà phát triển thực sự lười biếng thì bắt máy móc làm việc cho họ. Điều này một phần là do sự thúc đẩy của lợi ích bản thân, nhưng những nhà phát triển thông minh biết rằng con người không thể nâng công suất lên như máy móc được. Nếu bạn muốn lúc nào cũng hoàn thành công việc đúng hẹn, đáng tin cậy nào và bạn muốn nhân tố con người bị loại trừ nhiều nhất trong khả năng có thể chấp nhận được. Đối với mỗi vấn đề làm mình bị mất thời gian, tôi tự hỏi mình rằng– làm thế nào để mình có thể sẽ không bao giờ phải gặp vấn đề này một lần nữa? Nếu giải pháp của tôi có thể khắc phục được nó thì không còn ai sẽ phải gặp rắc rối với vấn đề đó nữa, đó cũng là một hiệu ứng phụ rất tốt.

Giờ đây, có một sự phân biệt khá rõ ràng giữa kiểu lười biếng thông minh, như đã mô tả ở trên– lười biếng khiến cho cuộc đời của mỗi chúng ta trở nên dễ dàng hơn một chút– và chỉ đơn giản là đừng nhấc mông bạn ra khỏi ghế. Nếu tôi đang xây dựng một công ty phần mềm, thì tôi sẽ nỗ lực để tìm thuê những người lười biếng nhất mà mình có thể.

Về tác giả bài viết:

Jeff_atwood_coding_horrorJeff Atwood là một chuyên gia công nghệ tại Mỹ, hiện đang sinh sống và làm việc tại Berkeley, CA. Anh là một kỹ sư phần mềm chuyên về công nghệ Microsoft .NET, và là một blogger nổi tiếng trong cộng đồng công nghệ với blog Coding Horror, anh là người sáng lập và kiêm Giám đốc điều hành (CEO) của trang web hỏi đáp uy tín Stack Overflow và cũng là đồng sáng lập của Stack ExchangeDiscourse.

Background Thread with IntentService

IntentService được sử dụng giống như một AssyncTask để tạo một thread riêng biệt so với main Thread trong activity với mục đích cập nhật UI.
Ưu diểm của IntentService là thread ko bị destroy khi activity bị destroy.
Giả sử như thread của bạn là download một file pdf nhưng trong quá trình download activity bị destroy (rotate màn hình , có cuộc gọi đến), nếu sử dụng AssyncTask thì quá trình download này bị ngắt quảng theo, còn sử dụng IntentService thì quá trình download vẫn được tiếp tục.
android.app.IntentService is a subclass of android.app.Service that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.
IntentService

Create our custom IntentService, MyIntentService.java

You have to implement constructor of MyIntentService(). If you only override the default constructor of MyIntentService(String name) from IntentService, RuntimeException of java.lang.InstantiationException with Unable to instantiate service will be thrown.

package com.example.androidintentservice;

import android.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {

 public static final String ACTION_MyIntentService = "com.example.androidintentservice.RESPONSE";
 public static final String EXTRA_KEY_IN = "EXTRA_IN";
 public static final String EXTRA_KEY_OUT = "EXTRA_OUT";
 String extraIn;
 String extraOut;

 public MyIntentService() {
  super("com.example.androidintentservice.MyIntentService");
 }

 @Override
 protected void onHandleIntent(Intent intent) {

  //get input
  extraIn = intent.getStringExtra(EXTRA_KEY_IN);
  extraOut = "Result from MyIntentService: Hello " + extraIn;

  //dummy delay for 5 sec
  try {
   Thread.sleep(5000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } //wait 3 sec

  //return result
  Intent intentResponse = new Intent();
  intentResponse.setAction(ACTION_MyIntentService);
  intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
  intentResponse.putExtra(EXTRA_KEY_OUT, extraOut);
  sendBroadcast(intentResponse);
 }

}

Modify layout to add a TextView to display result

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <TextView
        android:id="@+id/result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Main code in MainActivity.java. In order to receive result from IntentService, implement BroadcastReceiver, register in onCreate() and unregister in onDestroy(). To start MyIntentService, call startService() with coresponding intent.

package com.example.androidintentservice;

import android.os.Bundle;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.TextView;

public class MainActivity extends Activity {

 TextView textResult;

 private MyBroadcastReceiver myBroadcastReceiver;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  textResult = (TextView)findViewById(R.id.result);

  //Start MyIntentService
  Intent intentMyIntentService = new Intent(this, MyIntentService.class);
  intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_IN, "Android-er");
  startService(intentMyIntentService);

  myBroadcastReceiver = new MyBroadcastReceiver();

  //register BroadcastReceiver
  IntentFilter intentFilter = new IntentFilter(MyIntentService.ACTION_MyIntentService);
  intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
  registerReceiver(myBroadcastReceiver, intentFilter);
 }

 @Override
 protected void onDestroy() {
  super.onDestroy();
  //un-register BroadcastReceiver
  unregisterReceiver(myBroadcastReceiver);
 }

 public class MyBroadcastReceiver extends BroadcastReceiver {

  @Override
  public void onReceive(Context context, Intent intent) {
   String result = intent.getStringExtra(MyIntentService.EXTRA_KEY_OUT);
   textResult.setText(result);
  }

 }

}

Modify AndroidManifest.xml to add <service> of MyIntentService

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidintentservice"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.androidintentservice.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="com.example.androidintentservice.MyIntentService"></service>
    </application>

</manifest>

Lập trình viên cũng là người sử dụng

Bài viết được dịch từ blog Coding Horror

Hiện tại tôi đang phụ trách việc xây dựng một API nhỏ cho BetaBrite-specific một tập con của Alpha Sign Communications Protocol. Về mặt tự nhiên, tôi muốn nó trở nên dễ dàng sử dụng và dễ hiểu đối với những lập trình viên khác– đó là một vấn đề về usability (tính dễ sử dụng) kinh điển. Bạn sẽ áp dụng hướng tiếp cận usability nào khi mà độc giả của bạn lại là những nhà phát triển phần mềm khác?

Lập trình viên cũng chính là người sử dụng khi dùng các API của các đồng nghiệp khác viết ra.Lập trình viên cũng chính là người sử dụng khi dùng các API của các đồng nghiệp khác viết ra.


Câu trả lời thì cũng không mấy ngạc nhiên, nó chính xác giống như cách mà bạn thực hiện đối với người dùng bình thường. Steven Clarke của công ty Microsoft có một blog hoàn toàn tập trung vào chủ đề này, và ông ta hiện đang tiến hành những nghiên cứu về tính usability (tính dễ hiểu) đều đặn trên toàn bộ các sản phẩm công nghệ sắp ra lò của hãng Microsoft. Có một cái gì đó tóm tắt dày đặc trong những kết quả của ông ta (trong file powerpoint), và tất cả nó giống như là bánh-mì-và-bơ của các nghiên cứu về tính usability vậy. Không có thứ gì là quá thiên về các lập trình viên ở trong đó cả. Nó tập trung vào con người, các use case, các tác vụ, và có rất nhiều sự quan sát. Ở đây là một ít ví dụ tiêu biểu từ blog của ông ta, cái mà tôi đề xuất bạn nên xem qua.

Về các Attribute (thuộc tính):

Tôi nghĩ rằng một yếu tố trong vấn đề này đó là khả năng khó nhìn thấy của các attribute (thuộc tính). Ví dụ, một người tham gia trong cuộc nghiên cứu tuần này đã sử dụng debugger để duyệt qua những dòng code khi anh ta để ý thấy một hành vi (behavior) không mong đợi tại một thời điểm trong suốt quá trình thực thi của đoạn code đó. Anh ta đã tập trung vào một khối code riêng biệt và sử dụng nỗ lực của mình vào việc tìm hiểu xem liệu có phải khối code đó có thể là nguyên nhân của cái hành vi (behavior) mà anh ta đã quan sát thấy hay không. Nguyên nhân của cái hành vi đó là do bởi một thuộc tính (attribute) đã được áp dụng tới class đó để định nghĩa phương thức mà anh vừa mới duyệt qua. Vì vậy khi anh ta đọc đoạn code của mình, thì cái thuộc tính đó lại nằm ngoài sự tập trung chú ý của anh.

trên ADO.NET:

Trong một nghiên cứu về tính dễ sử dụng (usability) khác mà tôi tiến hành trên ADO.NET, những người tham gia đã được yêu cầu viết code để truy vấn một table và xuất ra các kết quả trên màn hình console. Những kết quả này được lưu trữ trong một DataReader. Nhiều người tham gia mong chờ tìm thấy một thuộc tính Count trên DataReader đó để họ có thể sử dụng một vòng lặp qua nội dung của DataReader, và đánh chỉ số mỗi thành phần trong mỗi lần lặp của vòng lặp đó. Tuy nhiên, không có thuộc tính nào như vậy tồn tại cả. Những người tham gia đã dành một số lượng lớn thời gian để tìm kiếm một số thuộc tính tương tự khác như là Length, NumberOfRows, v.v… nhưng vẫn không tìm thấy bất cứ cái gì có thể giúp ích cho họ.

Đến lúc này, thì hầu hết những người tham gia sẽ tìm đến tài liệu trợ giúp (help docs) để tìm một đoạn code mẫu có thể giúp họ. Ngay khi người tham gia tìm thấy một đoạn code mẫu mà chỉ cho họ rằng họ cần sử dụng một IEnumerator để duyệt qua nội dung của DataReader đó, họ hiểu chính xác điều cần phải làm. Thậm chí mặc dù giải pháp đó có khác một chút so với giải pháp mà những người tham gia đã thử ban đầu, nhưng họ cũng không gặp khó khăn gì trong việc hiểu hướng tiếp cận mới này.

Khi nói đến tính usability (tính dễ sử dụng), thì không có cái nào có thể thay thế được sự quan sát. Nó là tuyệt đối quan trọng.

Có một thứ đã xuất hiện trong đầu tôi hầu như ngay lập tức đó là sự quan trọng của những đoạn code mẫu tốt. Chúng là những thứ đầu tiên mà tôi tìm kiếm trong bất kỳ một môi trường mới nào mà mình bị đặt vào, và dù cho là tốt hơn hoặc tồi hơn, chúng lên dây cho kinh nghiệm của tôi. Nhưng, như Steven đã mô tả trong bài viết Dr. Dobb’s Journal, là Measuring API Usability (Đo lường tính dễ sử dụng của API) (PDF), bạn phải cẩn thận rằng bạn không thể che lấp được những quyết định thiết kế API tồi thông qua những đoạn code mẫu tốt được:

Kinh nghiệm đã thực sự giúp đỡ chúng tôi nhận ra nguyên nhân gốc của vấn đề mà chúng tôi đã quan sát thấy trong phòng thí nghiệm về tính usability (tính dễ sử dụng). Ví dụ, trong một nghiên cứu, chúng tôi quan sát thấy có rất nhiều các lập trình viên đang dành một phần lớn thời gian để tìm kiếm trong các tài liệu trợ giúp một vài đoạn code mẫu mà sẽ chỉ cho họ cách làm thế nào để có thể hoàn thành một tác vụ được yêu cầu. Cách hiểu đầu tiên của dữ kiện này chỉ đơn giản là “Hãy sửa lại tài liệu trợ giúp đó sao cho dễ hiểu!” Tuy nhiên, khi chúng tôi sử dụng framework kinh nghiệm để mô tả vấn đề này, thì nó trở nên rõ ràng rằng lý do mà các lập trình viên đã không thành công khi họ tìm kiếm qua tài liệu trợ giúp là bởi vì cái mà họ đang tìm kiếm thì đơn giản lại không tồn tại. Cái API mà họ đang làm việc trên đó cung cấp một tập các sự trừu tượng với mức sai lệch đối với những lập trình viên sử dụng nó. Họ mong chờ một kiểu trừu tượng đặc biệt được đưa ra bởi API nhưng nó lại không có, bởi vậy họ không thể tìm thấy bất cứ thứ gì về nó trong tài liệu trợ giúp. Theo suy luận đó, nhóm phát triển API đã thiết kế lại API để đưa ra những thứ mà giống với các lập trình viên sử dụng API đang mong chờ. Khi chúng tôi kiểm thử lại API đó, thì đúng là nó đã hoạt động tốt hơn rất nhiều.

Đây là một vấn đề mà tôi đã từng được tiếp xúc trước đây. Nếu bạn đang phải trả lời cho rất nhiều câu hỏi từ những lập trình viên khác về một số thứ mà bạn đã viết ra, thì nó có thể không phải bởi đồng nghiệp của bạn kém cỏi đâu.

Thất bại là mẹ thành công

Tôi tìm thấy đoạn trích này của Will Wright, từ một hội thảo tuần rồi, khá là thú vị:

Will Wright nói rằng anh ta đã học được bài học rằng hầu hết mọi dự án đều dường như có vẻ rất quyến rũ trên giấy tờ, nhưng đều bị thất bại khi ra thị trường. “Tôi thực sự đã hỏi các ứng viên khi tôi tuyển dụng là có bao nhiêu thất bại mà họ đã làm việc trên đó,” anh nói, “và tôi thực sự thích tuyển dụng một ai đó dựa trên số lần thất bại mà họ đã trải qua. Tôi nghĩ rằng đó là một hệ thống học tập tốt nhất.”

lap-trinh-vien-that-bai

Là một lập trình viên, có khả năng là bạn đang làm việc trên một dự án mà sẽ cầm chắc thất bại. Mỗi thất bại nên được xem như là một cơ hội tuyệt vời để nhận ra cái gì khônglàm việc, và tại lý do tại sao. Như trong một giai thoại về nhà phát minh lỗi lạc Thomas Edison:

Tôi cứ suy nghĩ mãi, đúng hơn là một chút cay đắng về cái thời điểm trong giai thoại nói về giai đoạn mà Thomas Edison đã cố thử nghiệm nhiều loại vật liệu khác nhau để tìm ra một vật liệu thích hợp cho bóng đèn sợi tóc. Ông đã thử hàng ngàn thành phần khác nhau nhưng đều thất bại. Một đồng nghiệp đã hỏi ông rằng liệu ông có cảm thấy đang lãng phí thời gian một cách vô ích, vì ông đã chẳng khám phá ra được điều gì. “Hầu như không,” Edison đáp trả một cách mạnh mẽ. “Tôi đã khám phá ra hàng ngàn thứ không phù hợp.”

Trong thực tế, sự khác nhau giữa thành công và thất bại có thể cuối cùng mấu chốt là ở chỗ cách bạn kiểm soát thất bại ra sao – như một bài viết trên tờ New Yorker đã minh họa về việc dự đoán sự thành công hoặc thất bại của các bác sĩ phẫu thuật:

Charles Bosk, một nhà xã hội học tại trường đại học Pennsylvania, đã tiến hành một nghiên cứu thông qua các cuộc phỏng vấn với những bác sĩ trẻ, những người mà đã xin thôi việc hoặc bị đuổi khỏi các chương trình huấn luyện về giải phẫu thần kinh, trong một nỗ lực để nhận ra điều gì làm tách biệt những bác sĩ phẫu thuật không thành công từ những người thành công khác.

Ông kết luận rằng, lý do còn xa hơn cả những kỹ năng về kỹ thuật hoặc trí thông mình, điều mà cần thiết cho thành công lại là kiểu thái độ mà người đó có – một nỗi ám ảnh đầu óc thực tế với khả năng và hậu quả của sự thất bại. “Khi tôi phỏng vấn những bác sĩ đã bị đuổi việc, tôi thường để cho cuộc phỏng vấn được thoải mái,” Bosk nói. “Tôi muốn nghe về những câu chuyện kinh khủng về việc mà họ đã làm sai, nhưng có một điều là họ lại không biết rằng điều họ đã làm là sai. Trong cuộc phỏng vấn của tôi, tôi bắt đầu phát triển cái mà tôi nghĩ là một dấu hiệu để nhận biết liệu một ai đó sẽ trở thành một bác sĩ phẫu thuật giỏi hay không. Nó là một cặp câu hỏi đơn giản: Bạn đã từng bao giờ phạm một sai lầm chưa? Và, nếu có, sai lầm tồi tệ nhất của bạn là gì? Những người mà nói rằng, ‘Tôi chưa thực sự phạm phải một sai lầm nào cả,’ hoặc, ‘Tôi có một vài kết quả tồi nhưng chúng là những thứ nằm ngoài tầm kiểm soát của tôi’ — lúc nào cũng vậy những người này là những ứng viên tồi nhất. Và những người mà nói rằng, ‘Tôi đã phạm rất nhiều sai lầm. Có một sai lầm khủng khiếp chỉ mới xảy ra ngày hôm qua thôi và đó là lý do mà tôi bị đuổi việc.’ Họ là những người tốt nhất. Họ có khả năng nghĩ lại về mọi thứ mà họ đã làm và tưởng tượng ra làm thế nào mà họ có thể hoàn thành công việc theo một cách khác.”

Điều này cũng nên là một câu hỏi phỏng vấn chính khi mà bạn tiến hành tuyển dụng. Phát triển phần mềm thường khó để có thể ở trong những điều kiện tốt nhất. Bạn luôn bị thất bại một số lần, và học được từ những thất bại đó theo một cách chân thực nhất. Nếu không thì, bạn đang lừa dối chính mình khỏi những cơ hội phát triển nghề nghiệp tốt nhất.