Cảm ơn bạn đã đọc và ủng hộ blog KTMT ʘ‿ʘ Từ bây giờ chúng tôi sẽ là kipalog.com !

sql
Comments

Khi sử dụng SQL, chắc hẳn các bạn đã biết có một cái gọi là NULL (mình gọi là cái vì NULL không phải là một giá trị) Để so sánh với NULL thì các bạn sẽ dùng toán tử isis not thay vì =(equal) hoặc là <>(not equal)

null.sql
1
SELECT * WHERE field IS NULL

Bạn đã bao giờ tự hỏi tại sao lại không dùng (=) và (<>). Đầu tiên chúng ta hãy thử nhé

null.sql
1
2
3
4
5
select "1" where 1 <> NULL;
select "2" where 1 IS NOT NULL;
select "3" where NULL = NULL;
select "4" where NULL <> NULL;
select "5" where NULL IS NULL;

và kết quả nhận được sẽ là “2” và “5”. Từ đấy có thể thấy một điều đơn giản là: để so sánh với NULL chúng ta chỉ có thể dùng IS và IS NOT. Vậy nếu bạn thực hiện các phép toán với NULL thì sao? Mọi phép toán như cộng ,trừ ,nhân ,chia ,concat mà có sự tham gia của NULL đều cho kết quả là NULL cả.

Đầu tiên để hiểu lý do thì chúng ta phải biết là tại sao lại có giá trị NULL. Giá trị NULL trong SQL mục đích chính là để nhằm tạo ra một cách thể hiện cho cái gọi là “không có thông tin hoặc thông tin không phù hợp” (missing information and inapplicable information). Do đó về mặt tự nhiên, bạn sẽ nói là field X không có thông tin, chứ sẽ không nói là field X bằng (equal) không có thông tin, đúng không. Tuy nhiên lý do trên không có giá trị về mặt giải thích.

Để giải thích cặn kẽ thì chúng ta phải đi lại một chút về khái niệm Logic. Về mặt toán học thì có rất nhiều loại LOGIC. Boolean logic được biết đến nhiều nhất. Bản chất của Boolean là chỉ tồn tại 2 giá trị TRUE, FALSE và các phép toán trên chúng. Do đó Boolean được xếp vào loại 2VL (2 valued logic). Tuy nhiên logic trong SQL rất tiếc lại không phải Boolean, vì trong SQL sẽ tồn tại 3 khái niệm logic: TRUE, FALSE, và Unknown ( hay chính là NULL ). Do đó logic trong SQL gọi là 3VL (3 valued logic). Trong Boolean chỉ có 2 phép so sánh là equal (=) và not(<>), tuy nhiên với 3VL như SQL sẽ có thêm phép so sánh là ISIS NOT. Kết hợp 3 loại giá trị với các phép so sánh đó sẽ cho chúng ta kết quả là 3 loại bảng truth table dưới đây:

(trích dẫn từ Wikipedia)

Trên đây là những kiến thức hết sức basic về giá trị NULL trên SQL, hy vọng có thể giúp các bạn đỡ nhầm lẫn khi thực hiện các phép toán với NULL.

Comments

Ở bài viết trước tôi đã đề cập đến cách sử dụng mock để viết unittest. Unittest có tác dụng không chỉ trong việc đảm bảo các đoạn code mới được viết thêm không phá vỡ các yêu cầu logic trước đó, trong bài viết này, tôi sẽ chia sẽ các kinh nghiệm sử dụng unittest để refactoring các đoạn code

Một trong những vấn đề khi viết các hàm đó là các hàm thường quá phức tạp. Robert Martin trong cuốn sách “Clean code - a handbook of Agile Software Craftmenship” đã nói về nói về các quy tắc khi thiết kế hàm: Quy tắc đầu tiên của function đó là chúng nên nhỏ, quy tắc thứ hai là chúng nên nhỏ hơn thế nữa" (The first rule ò functions is that they should be small. The second rule of functions is that they should be smaller than that). Function càng ngắn thì càng dễ hiểu, function càng ngắn thì nó càng tách biệt so với các hàm khác. Và hơn thế nữa hàm càng ngắn, thì test càng đơn giản. Vậy làm thế nào để biết function bạn viết là đủ ngắn hay chưa? Nếu để test 1 hàm cần tới hơn 20 dòng code, theo bản thân tôi, hàm đó nên được viết lại.

Hãy xét một ví dụ sau

models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Order(models.Model):
    # define fields in here


    def create_final_pdf_file(self, client_order_id):
        front_image = self.tree.create_frontcover_image()
        back_image = self.tree.create_backcover_image(client_order_id)

        if self.order_type == Order.SOFT_COVER:
            frontcover_file = pdf.create_pdf_from_images(
                [image_helper.save_image(front_image), None], self.book_size)
            backcover_file = pdf.create_pdf_from_images(
                [None, image_helper.save_image(back_image)], self.book_size)
            input_files = [frontcover_file, self.cached_pdf_file, backcover_file]
        elif self.order_type == Order.HARD_COVER:
            hardcover_image = self.create_hardcover_image(back_image, front_image)
            hardcover_size = self.PDF_SIZES[self.size_type]
            hardcover_file = pdf.create_pdf_from_images(
                [image_helper.save_image(hardcover_image)], hardcover_size)
            input_files = [hardcover_file, self.cached_pdf_file]

        new_path = pdf.merge_pdf_files(input_files)
        return new_path

Hàm create_final_pdf_file nhận tham số là một client_order_id, tạo ra một pdf file tương ứng client_order_id, và trả về đường dẫn của pdf file đó. Hàm này tạo ra một ảnh cover trước, và ảnh cover sau, sau đó ghép với một file pdf có sẵn để tạo nên final_pdf.

Tuy nhiên tuỳ theo giá trị của self.order_type mà cách tạo các ảnh trước và ảnh sau là khác nhau.

Nếu chỉ dừng ở đây, bản thân tôi, thấy khá hài lòng với hàm create_final_pdf_file. Hàm dài vừa đủ, không quá dài (19 lines), chỉ có một input đầu vào, và 1 output đầu ra. Tuy nhiên, nếu viết testcase cho hàm này, chúng ta sẽ thấy có vấn đề

test_models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class TestModel(unittest.TestCase):
    def setUp(self):
        tree = Tree()
        self.order = Order(tree=tree)

    @mock.patch('StoryTree.helpers.image_helper.save_image')
    @mock.patch('StoryTree.helpers.generator.pdf.merge_pdf_files')
    @mock.patch('StoryTree.helpers.generator.pdf.create_pdf_from_images')
    def test_create_final_pdf_file_soft_cover(
            self, mock_create_pdf, mock_merge_pdf, mock_save_image):
        self.order.order_type = Order.SOFT_COVER

        mock_cached_pdf = PropertyMock(
            return_value='StoryTree/tests/fixtures/1.pdf')
        mock_backcover = mock.Mock(return_value=image)
        mock_frontcover = mock.Mock(return_value=image)

        with nested(
                mock.patch.object(Order, 'cached_pdf_file', mock_cached_pdf),
                mock.patch.object(Tree, 'create_backcover_image', mock_backcover),
                mock.patch.object(Tree, 'create_frontcover_image', mock_frontcover)):
            self.order.create_final_pdf_file('STORYTREE01111')

            self.assertEqual(1, mock_backcover.call_count)
            self.assertEqual(1, mock_frontcover.call_count)
            self.assertEqual(2, mock_create_pdf.call_count)
            first_args = mock_create_pdf.call_args_list[0][0]
            self.assertTrue(first_args[0][-1] is None)

    @mock.patch('StoryTree.helpers.image_helper.save_image')
    @mock.patch('StoryTree.helpers.generator.pdf.merge_pdf_files')
    @mock.patch('StoryTree.helpers.generator.pdf.create_pdf_from_images')
    def test_create_final_pdf_file_hard_cover(
            self, mock_create_pdf, mock_merge_pdf, mock_save_image):
        self.order.order_type = Order.HARD_COVER

        mock_cached_pdf = PropertyMock(
            return_value='StoryTree/tests/fixtures/1.pdf')
        mock_backcover = mock.Mock(return_value=image)
        mock_frontcover = mock.Mock(return_value=image)
        mock_hardcover = mock.Mock(return_value=image)

        with nested(
                mock.patch.object(Tree, 'create_backcover_image', mock_backcover),
                mock.patch.object(Tree, 'create_frontcover_image', mock_frontcover),
                mock.patch.object(Order, 'cached_pdf_file', mock_cached_pdf),
                mock.patch.object(Order, 'create_hardcover_image', mock_hardcover)):
            self.order.create_final_pdf_file('STORYTREE01111')
            self.assertEqual(1, mock_backcover.call_count)
            self.assertEqual(1, mock_frontcover.call_count)
            self.assertEqual(1, mock_hardcover.call_count)
            self.assertEqual(1, mock_create_pdf.call_count)

Đoạn code test trên có vấn đề gì? Để test hàm create_final_pdf_file, chúng ta cần viết 2 test case, cho 2 trường hợp trong đoạn code if-else. Và 2 đoạn code test bị lặp lại khá nhiều, đặc biệt là ở việc mock các objects. Chúng ta có thể viết lại test case gọn hơn bằng cách viết một function chung, hoặc một function tạo ra các mock object và gọi nó trong từng hàm test. Nhưng liệu có phải đó là vấn đề chính.

Điều tôi muốn nói ở đây là: Code smell trong test code có nguyên nhân từ test code, hay từ bản thân đoạn code chúng ta muốn test. Hãy xem lại hàm create_final_pdf_file. Hàm nãy đã thực sự tốt? Một hàm tốt, là một hàm chỉ nên làm một việc. Hàm create_final_pdf_file ở đây, ngoài việc gọi các hàm khác, còn thêm vào nó đoạn xử lý logic xét kiểu của order. Đoạn code if-else xử lý 2 logic khác nhau, chúng nên được tách ra thành một hàm khác.

models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Order(models.Model):

    def create_input_files(self, front_image, back_image):
        if self.order_type == Order.SOFT_COVER:
            frontcover_file = pdf.create_pdf_from_images(
                [image_helper.save_image(front_image), None], self.book_size)
            backcover_file = pdf.create_pdf_from_images(
                [None, image_helper.save_image(back_image)], self.book_size)
            input_files = [frontcover_file, self.cached_pdf_file, backcover_file]
        elif self.order_type == Order.HARD_COVER:
            hardcover_image = self.create_hardcover_image(back_image, front_image)
            hardcover_size = self.PDF_SIZES[self.size_type]
            hardcover_file = pdf.create_pdf_from_images(
                [image_helper.save_image(hardcover_image)], hardcover_size)
            input_files = [hardcover_file, self.cached_pdf_file]
        return input_files

    def create_final_pdf_file(self, client_order_id):
        front_image = self.tree.create_frontcover_image()
        back_image = self.tree.create_backcover_image(client_order_id)
        input_files = self.create_input_files(front_image, back_image)
        new_path = pdf.merge_pdf_files(input_files)
        return new_path

Hàm create_final_pdf_file sau khi được refactoring, đã trở nên đơn giản và dễ đọc hơn, thay vì phải lướt qua 19 lines, và đọc hiểu logic của đoạn code if-else, giờ đây bạn có thể hiểu nó chỉ bằng create_input_files. Và code test mới cho hàm create_final_pdf_file như sau

test_models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestModel(unittest.TestCase):
    def setUp(self):
        self.order = Order()

    @mock.patch('StoryTree.helpers.generator.pdf.merge_pdf_files')
    def test_create_final_pdf_file(self, mock_merge_pdf):
        mock_cached_pdf = PropertyMock(
            return_value='StoryTree/tests/fixtures/1.pdf')
        mock_backcover = mock.Mock(return_value=image)
        mock_frontcover = mock.Mock(return_value=image)
        mock_input_files = mock.Mock(return_values=[''])

        with nested(
                mock.patch.object(Order, 'create_input_files', mock_input_files)
                mock.patch.object(Order, 'cached_pdf_file', mock_cached_pdf),
                mock.patch.object(Tree, 'create_backcover_image', mock_backcover),
                mock.patch.object(Tree, 'create_frontcover_image', mock_frontcover)):
            self.order.create_final_pdf_file('STORYTREE01111')

            self.assertEqual(1, mock_backcover.call_count)
            self.assertEqual(1, mock_frontcover.call_count)
            self.assertEquals(1, mock_input_files.call_count)

Việc tách logic của đoạn code tạo 2 input files ra thành một hàm create_input_files, làm cho hàm create_final_pdf dễ hiểu hơn, nói cách khác, nó che giấu thông tin không cần thiết cho lập trình viên khi đọc tới đoạn code của create_final_pdf. Hàm create_final_test giờ đây không làm gì khác ngoại việc gọi tới các hàm khác. Không có bất cứ logic nào được đặt trong hàm này. Trên thực tế rất nhiều lập trình viên sẽ không viết test cho những hàm như create_final_pdf nữa. Họ chỉ cần viết test cho 4 hàm create_input_files, create_backcover_image, create_frontcover_image, và cached_pdf_file là đủ.

Tóm lại, bạn có thể tìm kiếm code smell trong unittest, và refactoring hàm mà unittest đó muốn test

Comments

Quy tắc xoắn ốc

Có một quy tắc gọi là “quy tắc xoắn ốc” cho phép lập trình viên C/C++ phân tích trong đầu bất cứ khai báo nào. Quy tắc này rất đơn giản như sau:

  • Bắt đầu bằng tên biến và di chuyển xoắn ốc theo chiều kim đồng hồ.
  • Nếu gặp từ khóa const thì đối tượng nằm trên đường xoắn ốc trước const không đổi.
  • Nếu gập ký tự * thì đối tượng trước khi đến * là một con trỏ.
  • Nếu gặp [] thì đối tượng trước [] là một mảng.
  • Nếu gặp (kiểu dữ liệu 1, kiểu dữ liệu 2), đối tượng trước đó là một function.

Áp dụng

Ta tử áp dụng quy tắc trên để phân tích thử 1 số khai báo.

1. Khai báo đơn giản

                 +-------+
                 | +-+   |
                 | ^ |   |
            char *str[10];
             ^   ^   |   |
             |   +---+   |
             +-----------+

Ý nghĩa: str là một mảng (10 ký tự) con trỏ có kiểu dữ liệu ký tự (char).

2. Con trỏ hàm

          +--------------------+
          | +---+              |
          | |+-+|              |                    
          | |^ ||              |
     char *(*fp)( int, float *);
      ^   ^ ^  ||              |
      |   | +--+|              |
      |   +-----+              |
      +------------------------+

Ý nghĩa: fp là 1 con trỏ, trỏ đến 1 function (nhận 2 đối số là int, và con trỏ kiểu float), trả về 1 con trỏ có kiểu dữ liệu ký tự (char).

3. Const

  • Ví dụ 1

    char greeting[] = "Hello";
           +-----------------+
           |   +-----------+ |
           |   | +-------+ | |
           |   ^ ^       v | | 
    const char * p = greeting;
       ^   ^   ^         | | |
       |   |   |         | | |
       |   |   +---------+ | |
       |   +---------------+ |   
       +---------------------+

Ý nghĩa: p có giá trị giống greeting, và là 1 con trỏ, trỏ đến dữ liệu kiểu char, giá trị này là hằng số (không thay đổi).

  • Ví dụ 2

    char greeting[] = "Hello";
             +------------------------+   
             | +-------------------+  |
             | |     +-----------+ |  |
             | |     | +-------+ | |  |
             | |     ^ ^       v | |  |
    const char * const p = greeting;  |
       ^     ^ ^     ^         | | |  |
       |     | |     |         | | |  |
       |     | |     +---------+ | |  |
       |     | +-----------------+ |  |
       |     +---------------------+  |
       +------------------------------+

Ý nghĩa: p có giá trị giống greeting, giá trị của p không đổi, và nó là 1 con trỏ, trỏ đến dữ liệu kiểu chả, và dữ liệu của là hằng số.

Cuối cùng

Quy tắc xoắc ốc giúp ta hiểu ý nghĩa khai báo C/C++ một cách dễ dàng. Bây giờ với quy tắc này, bạn chắc chắn khai báo dưới đây dễ hiểu như ăn kẹo rồi phải không? void (signal(int, void (fp)(int)))(int);

Copyright © 2015 kỹ thuật máy tính