Các bước trưởng thành của người code VBA

Euler

Administrator
Thành viên BQT
Tôi dựa vào kinh nghiệm từ bản thân mình viết nên topic này. Tôi dựa vào quan điểm, lấy quy mô dữ liệu làm thước đo, vì vậy không thể tránh khỏi khác biệt cách nghĩ đối với mọi người.
Dù là quy mô dữ liệu ra sao, thì các kiến thức nền tảng không bao giờ là thừa. Dù là quy mô dữ liêu ra sao, thì thiết kế trước khi code bao giờ cũng là thói quen tốt.
Nhưng thực tế như ta thấy, nếu chỉ code cỡ 100 dòng, hiếm có ai thiết kế trước khi code. Do đó, dựa trên thực tế trải nghiệm của bản thân, ở giai đoạn nào cần cái gì, tôi viết nên topic này.

1. Đầu tiên các bạn sẽ code với sheet.
Bạn cần đăng nhập để thấy đính kèm

Bất cứ ai học VBA cũng sẽ đi qua bước này. Các bài hỏi đáp VBA phần lớn trên các diễn đàn ở Việt Nam cũng nằm ở giai đoạn này.
Các bạn có dữ liệu trên một sheet, và sẽ dùng macro để xử lý dữ liệu trên sheet này.

Có một rắc rối nho nhỏ với người mới học, khối lượng dữ liệu biến thiên, lúc này bạn cần tới công thức tính dòng cuối, ta vẫn thường thấy mọi người gọi nó là rend trong các đoạn code.
Cũng có người chẳng cần biết công thức tính rend là gì, data của mình giỏi lắm cỡ 50.000 dòng dữ liệu, vậy thì cứ cho chạy tới 50.000 . Lúc này các bạn mới học sẽ có thêm vốn liếng về interge và Long. Muốn chạy tới 50.000 thì phải dùng tới Long rồi. Interge chỉ chạy tới cỡ 30.000 thôi.
Code như vậy thì thiếu chuyên nghiệp nhưng không sao. Các bạn mới học nên điều đó hiểu được. Nhưng sau vài năm sau vẫn cứ code như thế thì không được.

Có người còn chẳng nhớ công thức rend là gì, cần dùng đến thì lại đi copy. Vậy hãy thử gõ nó 50 lần, sau đó kiểm tra lại xem có bao nhiêu lần gõ sai. Đó là cách luyện tập, cũng như ta học từ mới tiếng anh, ta phải viết đi viết lại nhiều lần đó thôi.

2. Sau đó các bạn sẽ làm việc với nhiều sheet dữ liệu.
Bạn cần đăng nhập để thấy đính kèm

Lúc này, công việc phải xử lý sẽ phức tạp hơn một chút. Bạn sẽ phải làm quen với câu lệnh Sheets("Tên_Sheet").Activate .
Muốn làm việc với sheet nào thì phải thực thi lệnh trên. Tôi ví dụ muốn lấy rend của sheet2 thì đầu tiên việc cần làm đó là bạn phải kích hoạt-Activate cái sheet2 lên.
Nhiều người có kinh nghiệm một chút thì nhanh nhảu không nên dùng lệnh này, sẽ gây cho chương trình chạy chậm. Trước hết, nói với các bạn mới học, khoan hãy nói tới việc nhanh chậm, hãy cố gắng tạo ra output chính xác cái đã.
Còn với những người có kinh nghiệm một chút, biết làm việc với mảng, tôi muốn nói với các bạn rằng, để ghi dữ liệu được ra mảng thì cũng phải xác định được cái rend của sheet đó. Và để xác định được rend một cách chính xác 100% thì phải kích hoạt sheet đó lên để tìm rend, sau đó muốn làm gì thì làm.
Workbooks("Tên_workbook").Sheets("Tên_Sheet").Activate .
Khi nhiều workbook, nhiều sheet đang làm việc, nếu không kích hoạt rõ ràng, sẽ dẫn tới lỗi có thể xảy ra khi lấy rend. Chúng ta hạn chế kích hoạt chứ không phải là KHÔNG KÍCH HOẠT. Việc hạn chế thì sẽ hạn chế nhưng khi cần thì PHẢI LÀM KHÔNG RUN TAY.

3. Làm việc với nhiều file.
Bạn cần đăng nhập để thấy đính kèm

Lúc này các xử lý đã bắt đầu phức tạp hơn. Một lần nữa, tôi phải nhắc lại với những bạn có chút kiến thức nhưng mồm miệng thì nhanh nhảu, chúng ta một lần nữa phải sử dụng lệnh kích hoạt-Activate một cách không run tay. Bạn có thể hạn chế sử dụng với số lần ít nhất, chứ không được nói TÔI SẼ TUYỆT ĐỐi KHÔNG DÙNG.
Như tôi nói ở trên, chúng ta cần phải chỉ định chính xác là làm việc với workbook nào (file excel nào) và sheet nào.

Đây sẽ là nội dung chủ yếu của xây dựng Tool-đó là làm việc với nhiều file và nhiều sheet dữ liệu. Lần đầu tôi làm chương trình như vậy cách đây đã khoảng chục năm, lúc ấy tôi chưa học thiết kế chương trình, cũng chẳng biết gì debug F8 hay F5 gì hết. Cứ chạy lỗi thì tìm và sửa.
Nghĩ lại khoảng thời gian đó, tôi cảm thấy mình đã bỏ ra công sức rất lớn. Rất nhiều tính toán xảy ra ở trong đầu. Việc hoàn thành chương trình và chạy đúng đã khiến tôi tự hào, vì mình đã xử lý thành công một khối lượng công việc khổng lồ. Nhưng nghĩ lại thì chẳng có gì tự hào cả. Mình đã không biết cách làm việc.

Từ kinh nghiệm đó, tôi thấy rằng, nếu code ở mức độ này, bạn cần học thiết kế.

Tôi từng rất sốt ruột khi cứ họp hành thảo luận, trong khi kỳ hạn thì sắp hết. Lúc đó admin tuhocvba an ủi như thế này:
-Yên tâm đi, thiết kế nên lâu. Khi đã fix thiết kế (thống nhất thiết kế) thì code nhanh lắm. Thời gian code chỉ mất khoảng 30% toàn bộ thời gian dự án.

Thiết kế nói ngắn gọn, chúng ta mô hình hóa các yêu cầu, diễn giải chúng bằng mô hình hoặc lời nói theo phương châm càng dễ hiểu càng tốt. Chúng ta liệt kê các chức năng, các thao thao tác người dùng mong muốn...
Toàn bộ những cái đó được viết ra, chúng ta không lưu giữ trong đầu nữa, nên đầu óc rất nhẹ nhàng, không phải căng ra. Mặt khác, người khác nhìn vào cũng hiểu được, nên có thể chia việc dễ dàng.

Đã bao giờ bạn code và sau một thời gian, bạn nhìn lại code của chính mình và không hiểu gì không? Việc đó không có gì là lạ, tôi cũng vậy. Nhưng nếu có bản thiết kế, cái bản thiết kế mà bất cứ ai nhìn vào cũng hiểu được ấy, bây giờ ta nhìn lại bản thiết kế ấy, chắc chắn ta sẽ hiểu ra ngay ta đã làm gì.
Việc update sửa chữa code sau này vì thế cũng thuận lợi hơn.

4. Làm việc với các ứng dụng khác.
Đó là từ Excel giao tiếp với Access, hoặc internet explorer (IE)...
Bạn cần đăng nhập để thấy đính kèm

Kỹ năng xử lý tính toán thì không đòi hỏi nhiều. Logic cũng không đòi hỏi cao. Tuy nhiên vì đã quen làm việc với Excel, bây giờ phải giao tiếp với ứng dụng khác,rõ ràng đó là một thách thức mới với người code VBA.
Nếu chưa một lần giao tiếp thử với IE, với Access,... bây giờ có một người nói, họ muốn code lấy dữ liệu từ web, hay lấy dữ liệu access về, vì chúng ta chưa có kinh nghiệm cho nên không dám trả lời có làm được hay không, hay là áng chừng cần bao nhiêu thời gian để làm xong. Như vậy thì vẫn chưa thể nói là trưởng thành.

Tất nhiên các bạn không thể biết hết, chẳng hạn nếu không có mối quan tâm về cad thì không code được, không có mối quan tâm về ddt2000 thì không code được,... nhưng IE và Access lại là chuyện khác, đó là thứ cơ bản mà các bạn nên một lần thử làm.

Lần đầu tiên giao tiếp với web, đó là tôi soạn sẵn bài viết trên excel rồi cho macro tự động post bài, tôi rất sung sướng.
Lần thứ hai giao tiếp với web, tôi code dạo cho người ta-kiếm chút tiền lẻ hihi.

Lần đâu tiên giao tiếp với access, đó là câu chuyện cũng rất thú vị mà có thời gian tôi sẽ viết sau.
 

NhanSu

SMod
Thành viên BQT
Mình thì không activate workbook khác để lấy dữ liệu, lỗi dòng cuối do người lập trình chưa chỉ định rõ workbook hay worksheet. Ví dụ sau sẽ mở 2 workbook test.xls và test2.xlsx, copy dữ liệu cột A ở sheet1 trong test.xls (đây không phải activesheet của test.xls) sang activesheet của test2.xlsx. Khi chạy, nếu dùng lệnh (1) (đặt WithWS=true) thì chuơng trình chạy bình thường còn dùng lệnh (2) (WithWS=false) thì chuơng trình sẽ báo lỗi Runtime error 1004: Method Range of worksheet failed. Lý do lỗi là khi mở workbook mới thì worbook đó sẽ là active, ở đây là test2.xlsx, trong lệnh (2) Rows.Count sẽ lấy số dòng trong active sheet của active workbook =1048576, lệnh (2) trở thành
Mã:
LastRow = ws.Range("A" & 1048576).End(xlUp).Row
vượt quá giới hạn của xls nên gây lỗi. Trong lệnh (1) ta chỉ định rõ ws.Rows.Count mà ws là sheet1 của file xls nên ws.Rows.Count=65536, không có lỗi.
Mã:
Option Explicit
#Const WithWS = True
Sub test()
    Dim wb As Workbook, ws As Worksheet, r As Range, wb2 As Workbook, ws2 As Worksheet
    Dim LastRow As Long, Data()
    Set wb = Workbooks.Open(ThisWorkbook.Path & "\test.xls")
    Set ws = wb.Worksheets("Sheet1")
    Set wb2 = Workbooks.Open(ThisWorkbook.Path & "\test2.xlsx")
    Set ws2 = wb2.ActiveSheet
#If WithWS Then
    LastRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row       '(1)
#Else
    LastRow = ws.Range("A" & Rows.Count).End(xlUp).Row       '(2)
#End If
    Set r = ws.Range("A1:A" & LastRow)
    Data = r.Value
    ws2.Columns(1).Clear
    ws2.Range("A1:A" & LastRow) = Data
    wb.Close False
    wb2.Close True
End Sub
 

Euler

Administrator
Thành viên BQT
Mình thì không activate workbook khác để lấy dữ liệu, lỗi dòng cuối do người lập trình chưa chỉ định rõ workbook hay worksheet.
[/code]
Mình xác nhận là, đã có trường hợp chỉ định rõ workbook và worksheet nhưng vẫn lỗi. Không có data để phản biện, dựa vào kinh nghiệm thực tế đã gặp.
Ngoài ra khi không active, các lệnh gán range.value = arr cũng có thể không được thự hiện. Không có data để phản biện, dựa vào kinh nghiệm thực tế đã gặp.
Bạn cần đăng nhập để thấy đính kèm

Khi cần thì các bạn cứ sử dụng Activate, dùng với số lần hạn chế nhất có thể.
 

giaiphapvba

Administrator
Thành viên BQT
Ở mức độ 3 như anh Euler đã nói, đó là cần phải thiết kế.
Nhiều người không quen cách làm việc: hỏi rõ thông tin , thiết kế, thống nhất thiết kế, code, kiểm tra code, vì vậy họ sốt ruột là phải.
Ở dự án code số 01, admin tuhocvba cũng bị thúc giục tiến độ. Bởi vì anh đã hỏi rất nhiều thông tin.

Người yêu cầu thì thiếu chuyên nghiệp. Thay vì memo thông tin dễ hiểu, họ đưa file code của họ (record macro) để đọc và tự hiểu nội dung yêu cầu.
Mọi người code VBA cũng thấy, đọc yêu cầu so với đọc code (code thì rối tung vì hầu như là record macro), thì đọc yêu cầu dễ hơn.
Bước hiểu yêu cầu rất quan trọng, hiểu sai sẽ code sai.

Hiểu yêu cầu không chỉ là logic data, mà thao tác người dùng cũng quan trọng. Không làm rõ cái này sẽ không ổn.
Ta ví dụ:
Bạn cần đăng nhập để thấy đính kèm


Đây là thao tác người dùng mong muốn.
1. Tình huống 1:
OutputTemp là thứ không phải là lý tưởng. Macro chỉ tạo ra được ở mức độ này. Do dữ liệu không thống nhất định dạng, do đó cần dừng lại để người dùng sửa chữa bằng tay.
Bước tiếp theo, một chức năng khác của macro, đó là lấy input là OutputTemp và File4 để tạo ra Output.
Nếu lý giải chính xác yêu cầu, thì code sẽ đi theo hướng như trên.

Nhưng nếu không lý giải chính xác, người code có thể chạy một mạch từ File1,File2,...,File4 để ra thẳng Output.
Việc sai hay đúng, sẽ ra thông báo bằng cảnh báo. Việc code như thế này vừa tốn thời gian khi phải xem xét có bao nhiêu loại cảnh báo.
Dữ liệu không thống nhất nên cũng khó code, trong khi đó nếu người dùng tự kiểm tra vào các vị trí sai mà macro đã tô màu và sửa lại thì nhanh hơn.

2. Tình huống 2:
Trong thực tế các file 1,2,3 không đổi,định dạng dữ liệu cũng là chuẩn vì được xuất từ máy ra. Nhưng nội dung File4 thay đổi hàng ngày hay hàng tuần (định dạng file không đổi vì được xuất từ máy ra).
Nếu thao tác bằng tay, người dùng sẽ tạo ra outputemp từ file1,2,3.
Sau đó sử dụng file outputemp và file 4 để tạo ra output.

Nếu định dạng file là chuẩn, không có lý do để dừng lại ở outputemp. Chúng ta biết rằng, khi macro phải dừng lại giữa chừng, thì công đoạn code phần tiếp theo sau đó sẽ rất vất vả. Các vị trí dòng, vị trí cột, các từ khóa... phải nạp lại từ đầu.

Vì là macro chạy, cho nên việc tốn công tốn sức chạy đồng loạt cả 4 file là điều bình thường, có phải do con người làm đâu. Vậy cách giải quyết là, mỗi lần chạy hãy cứ nạp cả 4 file vào để ra output. File4 mới hay cũ thì kết quả cũng được thể hiện vào output. Đây là nội dung cần thảo luận với người nêu yêu cầu.

Nói tóm lại làm rõ thao tác người dùng, thảo luận để thống nhất thiết kế là rất quan trọng. Việc thảo luận như vậy chỉ có thể tiến hành nếu xây dựng được bản thiết kế. Còn nếu chỉ dùng chữ để chat với nhau, hoặc thậm chí nói chuyện với nhau bằng lời, sẽ không làm rõ ý, và bỏ sót nhiều nội dung.

Vì vậy, học thiết kế là quan trọng. Bước khởi đầu, chính là các bạn phải nêu rõ ràng được yêu cầu của mình, trực quan và dễ hiểu. Như vậy, trong khi các bạn đang hỏi đáp trên diễn đàn, đó cũng là quá trình học hỏi rồi. Nhưng nhiều người lại tỏ ra bất mãn khi thấy BQT khắt khe với các bạn phải nêu yêu cầu dễ hiểu. Ai làm việc mà chẳng muốn dễ dàng và dễ thở, gian khổ nhưng có lợi ích thì chúng ta phải chấp nhận.
 
Em thấy nhiều người chưa học làm thợ đã muốn làm thầy. Code thì chưa đâu vào đâu đã lo giấu code.
Thiết kế là yêu cầu cao nhất, không cần biết giỏi như thế nào, nhưng không trình bày cho mọi người hiểu được thì cũng không ai dám dùng người ấy trong công việc, vì công việc thường là làm việc theo nhóm. Nế không diễn đạt tốt thì cũng không chia việc cho người khác được.
Đây là một mẩu tin tuyển dụng mà bình thường vẫn thấy:
Bạn cần đăng nhập để thấy hình ảnh

Thường là yêu cầu biết thiết kế chương trình VBA thì cần biết thêm về access.
Ở tuyển dụng 1: lương 40~70 triệu.
Ở tuyển dụng 2: lương 120 triệu.

Mọi người thường nhầm lẫn ngôn ngữ lập trình là giỏi. Ví dụ, anh ấy giỏi lắm vì anh ấy code C. Anh ấy giỏi lắm vì anh ấy code java...
Ngôn ngữ lập trình là công cụ. Còn giải quyết vấn đề là thiết kế.

Đối với VBA, ở VN chưa có những mức lương như nói ở trên, hướng đi tốt là anh chị mở lớp/trung tâm dạy VBA cho mọi người. Đây là hướng đi tốt nhất.
Việc viết sách, em nghĩ không có lãi, mục đích chính có lẽ để xây dựng thương hiệu, uy tín.

Với người dạy, nên dạy cho học viên theo hướng đi thiết kế, suy nghĩ logic. Mà đầu tiên là dạy họ cách truyền đạt thông tin dễ hiểu.
 

Euler

Administrator
Thành viên BQT
Hiện tại tôi chưa tái hiện được việc rend bị lỗi khi không activate. Nhưng thực tế trong quá khứ đã diễn ra việc như vậy.
Ở đây, tôi tái hiện được lỗi khi không activate thì không gán giá trị cho mảng được:
Bạn cần đăng nhập để thấy hình ảnh

Mở thật nhiều file excel lên, việc tái hiện sẽ dễ dàng.
Bạn cần đăng nhập để thấy hình ảnh


Khi activate thì việc gán giá trị cho mảng thực hiện thành công:
Bạn cần đăng nhập để thấy hình ảnh
 

NhanSu

SMod
Thành viên BQT
@Euler câu lệnh trên sai vì cells(1,1) và cells(rend,1) là các range của sheet hiện hành, không phải range của workbooks("book1").sheets(2). Để sửa lại mà không cần active book1, ta dùng lệnh (chú ý có các dấu . ở trước cells)
Mã:
with workbooks("book1").sheets(2)
arr=.range(.cells(1,1),.cells(rend,1))
end with
Câu lệnh tìm rend cũng có thể bị lỗi khi workbook hiện tại và book1 có 1 file là xls, 1 file là xlsx do Rows.Count là số dòng tối đa của workbook hiện hành, không phải của book1 (như ở bài #2 mình đã ví dụ).
 

Euler

Administrator
Thành viên BQT
@Euler câu lệnh trên sai vì cells(1,1) và cells(rend,1) là các range của sheet hiện hành, không phải range của
Tại sao lại là của sheet hiện hành khi mà tôi chỉ định rõ ràng workbook và sheet ở đây. Đường kẻ xanh:
Bạn cần đăng nhập để thấy hình ảnh


Cách viết của bạn và của tôi là tương đương mà thôi.
Tuy nhiên để khỏi thắc mắc, tôi sẽ dùng cách viết mà bạn nêu.
Bạn cần đăng nhập để thấy hình ảnh

Lỗi không tính được rend.

Thử nghiệm tiếp, chứng minh không gán được cho mảng:
Bạn cần đăng nhập để thấy hình ảnh


Nguyên liệu thực hiện thử nghiệm là 7 file đang mở, book1, book2,...book7.
Bạn cần đăng nhập để thấy hình ảnh


Bây giờ tôi cho lệnh activate, kết quả gán cho mảng OK.
Bạn cần đăng nhập để thấy hình ảnh
 

NhanSu

SMod
Thành viên BQT
Bạn đang thiếu dấu . trước các cells mà, phải là ".cells(1,1)" và ".cells(rend,1)"
 

NhanSu

SMod
Thành viên BQT
Nếu không có các dấu . này thì chỉ cần 1 mở file là có lỗi khi sheets(1) không phải là activesheet.
Mã:
Arr=sheets(1).range(cells(1,1),cells(2,2)).value    'loi
 

Euler

Administrator
Thành viên BQT
Nếu không có các dấu . này thì chỉ cần 1 mở file là có lỗi khi sheets(1) không phải là activesheet.
Mã:
Arr=sheets(1).range(cells(1,1),cells(2,2)).value    'loi
cảm ơn bạn, lỗi không tính được rend.

Bạn cần đăng nhập để thấy hình ảnh

Bạn cần đăng nhập để thấy hình ảnh


Code đây, bạn xem sai chỗ nào không:
Mã:
Sub esss()
    Dim rend As Long
    Dim arr
    rend = 10
    With Workbooks("book1").Sheets(1)
        rend = .Range(.Cells(1, 1), .Cells(rend, 1))
        arr = .Range(.Cells(1, 1), .Cells(rend, 1))
    End With
End Sub
 

tuhocvba

Administrator
Thành viên BQT
NhanSu nhận xét đúng đấy. Euler gõ sai rồi.
Mã:
Sub esss()
    Dim rend As Long
    Dim arr
    rend = 10
    With Workbooks("book1").Sheets(1)
        rend = .cells(.rows.count,1).End(xlUp).Row
        arr = .Range(.Cells(1, 1), .Cells(rend, 1))
    End With
End Sub
 

NhanSu

SMod
Thành viên BQT
Mình thường dùng Range("A1:A" & rend) nên không bị lỗi đó.
 

Euler

Administrator
Thành viên BQT
Mình thường dùng Range("A1:A" & rend) nên không bị lỗi đó.
Tôi activate thì không bao giờ lỗi. Activate 1000 lần mới sợ, chứ chỉ 10 lần đã là nhiều, không ảnh hưởng gì tới tốc độ.
Cho tôi hỏi, nếu để cột dạng số, thì code như trên còn hoạt động đúng nữa không bạn?
 

tuhocvba

Administrator
Thành viên BQT
Tôi activate thì không bao giờ lỗi. Activate 1000 lần mới sợ, chứ chỉ 10 lần đã là nhiều, không ảnh hưởng gì tới tốc độ.
Cho tôi hỏi, nếu để cột dạng số, thì code như trên còn hoạt động đúng nữa không bạn?
Thử thì biết, tính ầm ầm.
Mình thường dùng Range("A1:A" & rend) nên không bị lỗi đó.
Code không sai, nhưng để dễ hiểu thì nên viết With như bạn nói. Hoặc kích hoạt và làm việc như Euler. Viết tọa độ cells dễ đọc code.
Đây không phải là luật, là quy ước giữa mọi người với nhau khi làm việc, dễ đọc code của nhau, dễ hiểu.
 

BKKBG

Yêu THVBA nhất
Tại sao lại là của sheet hiện hành khi mà tôi chỉ định rõ ràng workbook và sheet ở đây. Đường kẻ xanh:
Bạn cần đăng nhập để thấy hình ảnh
Giải thích ngắn gọn như thế này:
Code sai:
Mã:
Sub test()
    Dim rend As Long
    Dim arr
    rend = 10
    arr = Workbooks("book1").Sheets(1).Range(Cells(1, 1), Cells(rend, 1)).Value
End Sub
Khi viết như thế này, thì các đối tượng Cells(1, 1), Cells(rend, 1) không rõ thuộc về đâu, mặc định thuộc về workbook và sheet hiện hành.
Cho nên để chắc chắn theo ý đồ người code thì phải viết:
Mã:
Sub test2_1()
    Dim rend As Long
    Dim arr
    rend = 10
    Workbooks("book1").Sheets(1).Activate
    arr = Workbooks("book1").Sheets(1).Range(Cells(1, 1), Cells(rend, 1)).Value
End Sub
Hoặc viết đầy đủ sẽ là:
Mã:
Sub test2_2()
    Dim rend As Long
    Dim arr
    rend = 10
    arr = Workbooks("book1").Sheets(1).Range(Workbooks("book1").Sheets(1).Cells(1, 1), Workbooks("book1").Sheets(1).Cells(rend, 1)).Value
End Sub
Chỉ định rõ Cells(1, 1) là của Workbooks("book1").Sheets(1) : Workbooks("book1").Sheets(1).Cells(1, 1)
Hoặc viết vắn tắt bằng :
Mã:
Sub test2_3()
    Dim rend As Long
    Dim arr
    rend = 10
    With Workbooks("book1").Sheets(1)
        arr = .Range(.Cells(1, 1), .Cells(rend, 1)).Value
    End With
End Sub
Tôi bình chọn cho cách cuối cùng. Nó cũng đúng khi tính dòng cuối khi mở nhiều file, trong file có nhiều sheet.

Tất nhiên, cách giải quyết như ở test2_1 thì cũng đúng. Để giảm thiểu ảnh hưởng tốc độ, cần chú ý sử dụng kèm theo:
Mã:
Application.ScreenUpdating = False
Kết thúc chương trình trả lại là:
Mã:
Application.ScreenUpdating = True
Giả sử có 5 file dữ liệu, mỗi file 2 sheets. Như vậy nếu đi theo cách làm test2_1 thì cần thực hiện lệnh kích hoạt sheet tổng cộng khoảng cỡ 10 lần.
Thời gian chạy thử là 0.08s.
Mã:
Sub thu()
    Dim rend As Long, i As Long
    Dim arr
    Dim t As Double
    rend = 10
    t = Timer
    Application.ScreenUpdating = False
        For i = 1 To 10 Step 1
            Workbooks("book1").Sheets(1).Activate
            arr = Workbooks("book1").Sheets(1).Range(Cells(1, 1), Cells(rend, 1)).Value
        Next i
    Application.ScreenUpdating = True
    
    Debug.Print "thoi gian thuc hien " & (Timer - t)
End Sub
Vì vậy, kết luận không ảnh hưởng tới tốc độ là chấp nhận được.

Về cách code:
Mã:
Workbooks("book1").Sheets(1).Range("A1:A" & rend)
Vì Range đã được gắn với workbook và sheet rồi nên Range không có vấn đề gì, bên trong để xác định địa chỉ Range, ở đây không dùng đối tượng cells mà dùng chuỗi ký tự "A1:A" & rend cho nên không xảy ra lỗi vặt. Còn khi dùng cell, vì cells là đối tượng nên cần chỉ định rõ cells đó thuộc về đâu, hoặc ngầm hiểu thuộc về workbook và sheet hiện hành nếu như không có chỉ định nào. Đây là nguyên nhân sinh ra lỗi nếu không có xử lý phụ (Kích hoạt workbook và sheet mong muốn trước khi thực hiện tính hay gán).

Từ tổng hợp trên, tôi thấy rằng dùng With trong trường hợp này là cách code khoa học nhất. Viết code ngắn gọn, dễ dàng đọc code, dễ hiểu.
 

NhanSu

SMod
Thành viên BQT
@Euler tất cả các đối tượng vùng bao gồm range, rows, columns đều cần chỉ định rõ workbook và sheet, nếu không có thì excel sẽ hiểu đó là workbook và sheet hiện hành. Thông thường lỗi này sẽ hiện ra ngay khi chạy đó là Runtime error 1004. Tuy nhiên có trường hợp chương trình vẫn chạy nhưng kết quả không chính xác, ví dụ như trong code bạn ví dụ ở trên (bài #6):
Hiện tại tôi chưa tái hiện được việc rend bị lỗi khi không activate. Nhưng thực tế trong quá khứ đã diễn ra việc như vậy.
Ở đây, tôi tái hiện được lỗi khi không activate thì không gán giá trị cho mảng được:
Bạn cần đăng nhập để thấy hình ảnh
Trong ví dụ này, giả sử ta đang mở nhiều file book1, book2... và vẫn chạy lệnh Workbooks("book1").sheets(2).activate nhưng có trường hợp code vẫn chạy không ra kết quả đúng. Giả sử code đầy đủ là
Mã:
sub test()
dim rend as long, arr()
workbooks.open thisworbook.path & "\book1.xlsx"
workbooks.open thisworbook.path & "\book2.xls"                                 ' workbook hien hành là book2.xls có 65536 dòng
rend=workbooks("book1").sheets(2).cells(rows.count,1).end(xlup).row  'lenh sai (1) rows.count=65536
workbooks("book1").sheets(2).activate
arr=range(cells(1,1), cells(rend,1)).value
end sub
Giả sử book1 có 100.000 dòng tuy nhiên do khi chạy lệnh (1) thì worbook hiện hành là book2 nên rows.count=65536 dẫn đến end(xlup) nhảy đến dòng đầu của dữ liệu, chương trình vẫn chạy nhưng kết quả bị sai, nếu activate trước khi tìm dòng cuối thì không bị lỗi này.
 

Euler

Administrator
Thành viên BQT
Cảm ơn các bạn đã đóng góp ý kiến, chia sẻ kinh nghiệm. Mình đã hiểu vấn đề rồi ạ.
Về nhận thức lỗi: Mình đã nhận thức có lỗi nên đã xử lý bằng Activate trước khi viết topic này.
Qua trao đổi với các bạn trong topic này, mình đã hiểu nguyên nhân lỗi. Mình đã thử các phép gán và tính dòng cuối khi dùng With, kết quả đều được như mong đợi.
Mã:
Sub Euler()
    Dim rend As Long
    Dim arr
  
    Workbooks("book3").Sheets(1).Activate 'Kich hoat mot workbook khong lien quan
    With Workbooks("book1").Sheets(1)
        rend = .Cells(Rows.Count, 1).End(xlUp).Row
        arr = .Range(.Cells(1, 1), .Cells(rend, 1)).Value
    End With
    
    With Workbooks("book2").Sheets(1)
        .Range(.Cells(1, 1), .Cells(rend, 1)).Value = arr
    End With
    
End Sub
 

NhanSu

SMod
Thành viên BQT
Vẫn thiếu dấu . ở trước Rows.Count bạn ơi, phải là .Rows.Count, nếu viết Rows.Count thì Rows là đối tượng thuộc về book3 dễ dẫn đến lỗi như mình ví dụ ở bài 18.
 
Top