OpenEdge i VS Code 2
Każdy z Was zapewne często korzysta z pomocy ze strony sztucznej inteligencji. Warto więc powracać do tego tematu od czasu do czasu.
W Progress OpenEdge ten temat też jest aktywnie rozwijany, ale zanim pojawi się na tym blogu jakiś wpis na temat produktu OpenEdge MCP zobaczmy co możemy zrobić w omawianym niedawno środowisku VS Code.
Chodzi o agenta AI GitHub Copilot, który jest bardzi przydatny do pisania kodu “w locie”.
Jest to narzędzie wyspecjalizowane (tzw. AI pair programmer), które działa bezpośrednio w edytorze IDE, np. VS Code.
Jego zaletą jest błyskawiczne autouzupełnianie kodu i generowanie całych funkcji na podstawie komentarzy bez opuszczania edytora. Poza tym Copilot „widzi” pliki, nad którymi aktualnie pracujemy, dzięki czemu lepiej dopasowuje sugestie do naszego stylu i struktury projektu.
Do korzystania z Copilota w VS Code wymagane jest konto na GitHub. Jest ono niezbędne do uwierzytelnienia subskrypcji i synchronizacji ustawień. Od grudnia 2024 roku dostępna jest wersja Copilot Free, która pozwala na darmowe korzystanie z narzędzia w VS Code (oczywiście są limity).
Po zainstalowaniu rozszerzenia GitHub Copilot w VS Code, zostaniemy poproszeni o zalogowanie się właśnie przez GitHub. Przy logowaniu widzimy trzy opcje, ponieważ GitHub (należący do Microsoftu) wspiera tzw. logowanie społecznościowe.

Loguję się korzystając z mojego wcześniej utworzonegfo konta.

W oknie chata Copilota podaję przykładową instrukcję:
Write a program in openedge ABL that:
1. Prompts the user for a customer number
2. Finds that customer in the Customer table
3. Calculates the total value of all orders for that customer by summing OrderLine.ExtendedPrice
4. Displays the results.
Instrukcje możemy napisać po polsku ale warto jednak pamiętać, że choć Copilot rozumie polski, to jego główne dane treningowe pochodzą z publicznych repozytoriów, gdzie dominuje język angielski. Dlatego precyzyjne terminy techniczne lub bardziej złożone instrukcje mogą czasem dawać lepsze rezultaty w języku angielskim.
Musimy poczekać kilka minut. Poniżej jest wygenerowana procedura.
/* total_customer_orders.p
Prompts for a customer number, finds the customer,
sums OrderLine.ExtendedPrice for that customer's orders,
and displays the total value.
*/
DEFINE VARIABLE iCustNo AS INTEGER NO-UNDO.
DEFINE VARIABLE decTotal AS DECIMAL NO-UNDO INITIAL 0.
DEFINE VARIABLE lHasLines AS LOGICAL NO-UNDO INITIAL FALSE.
PUT UNFORMATTED "Enter customer number: ".
IMPORT UNFORMATTED iCustNo.
FIND Customer NO-LOCK WHERE Customer.CustNum = iCustNo NO-ERROR.
IF NOT AVAILABLE Customer THEN DO:
MESSAGE "Customer " iCustNo " not found." VIEW-AS ALERT-BOX.
RETURN.
END.
FOR EACH Order NO-LOCK WHERE Order.CustNum = iCustNo NO-ERROR:
FOR EACH OrderLine NO-LOCK WHERE OrderLine.OrderNum = Order.OrderNum NO-ERROR:
ASSIGN decTotal = decTotal + OrderLine.ExtendedPrice
lHasLines = TRUE.
END.
END.
IF NOT lHasLines THEN
MESSAGE "No orders found for customer " iCustNo VIEW-AS ALERT-BOX.
ELSE
MESSAGE "Total value for customer " iCustNo ": " decTotal VIEW-AS ALERT-BOX.
RETURN.
Tu widzimy błędy jak np. NO-ERROR w pętli FOR EACH, czy sposób wprowadzenia numeru klienta. Stwórzmy plik z instrukcjami dla Copilota. Powinien on spowodować usunięcie NO-ERROR, zmienić sposób deklaracji zmiennych na nowocześniejszy a także wprowadzić pewne zmiany w formatowaniu.
.github/copilot-instructions.md
# Modern OpenEdge ABL Development Standards ## 1. Language Version - Target OpenEdge ABL 12 or newer only. - Do not generate legacy ABL syntax. ## 2. Variables - Always use `VAR` for local variables. - Never use `DEFINE VARIABLE`. - Do not specify `NO-UNDO` (default in modern ABL). - Use the equal sign (`=`) for initial values (e.g., `VAR INT i = 10`). ## 3. Data Types - Use full data type names only: `CHARACTER`, `INTEGER`, `DECIMAL`, `LOGICAL`. - Do not use abbreviated types. ## 4. Error Handling - Every procedure or class must start with: `BLOCK-LEVEL ON ERROR UNDO, THROW.` ## 5. Formatting - Use UPPERCASE for all ABL keywords. - Use 4-space indentation. - One statement per line. ## 6. Data Retrieval - Don't use NO-ERROR with FOR loops.
Powtarzamy zlecenie najlepiej dla nowego czata.
/* total_customer_orders2.p Prompts for a customer number, finds the customer, sums OrderLine.ExtendedPrice for that customer's orders, and displays the total value. */ BLOCK-LEVEL ON ERROR UNDO, THROW. VAR INTEGER iCustNo = 0. VAR DECIMAL decTotal = 0. VAR LOGICAL lHasLines = FALSE. PUT UNFORMATTED "Enter customer number: ". IMPORT UNFORMATTED iCustNo. FIND Customer NO-LOCK WHERE Customer.CustNum = iCustNo NO-ERROR. IF NOT AVAILABLE Customer THEN DO: MESSAGE "Customer " iCustNo " not found." VIEW-AS ALERT-BOX. RETURN. END. FOR EACH Order NO-LOCK WHERE Order.CustNum = iCustNo: FOR EACH OrderLine NO-LOCK WHERE OrderLine.OrderNum = Order.OrderNum: ASSIGN decTotal = decTotal + OrderLine.ExtendedPrice. ASSIGN lHasLines = TRUE. END. END. IF NOT lHasLines THEN MESSAGE "No orders found for customer " iCustNo VIEW-AS ALERT-BOX. ELSE MESSAGE "Total value for customer " iCustNo ": " decTotal VIEW-AS ALERT-BOX. RETURN.
Chciałbym żeby relacje były w skróconej formie ze słowem kluczowym OF, więc dodaję instrukcję na końcu pliku .github/copilot-instructions.md
– Establish relationships between two tables using the OF keyword
OK, trzecia próba wygenerowania procedury.
/* total_customer_orders3.p */
BLOCK-LEVEL ON ERROR UNDO, THROW.
VAR CHARACTER custNum = "".
VAR DECIMAL totalValue = 0.
DISPLAY "Enter Customer Number:".
UPDATE custNum.
FIND Customer WHERE Customer.CustNum = custNum NO-LOCK.
IF AVAILABLE Customer THEN
DO:
FOR EACH Order OF Customer NO-LOCK:
FOR EACH OrderLine OF Order NO-LOCK:
totalValue = totalValue + OrderLine.ExtendedPrice.
END.
END.
DISPLAY
"Customer Number:" Customer.CustNum
"Customer Name:" Customer.Name
"Total Order Value:" totalValue.
END.
ELSE
DO:
DISPLAY "Customer not found.".
END.
Chociaż nie instruowałem agenta o sposobie wprowadzenia numeru klienta, zmienił on sposób, ale niestety na błędny. Pole custnum nie może występować zanim rekord nie będzie w buforze, jednakże relacje sa napisane prawidłowo. Inny jest sposób podania wyniku co nie powinno nas dziwić, w końcu to sztuczna inteligencja.
No dobrze, ale co dostaniemy jeśli to zadanie skieruje do czataGPT. Nie zna on struktury bazy więc trzeba napisać, że chodzi o bazę testową sports2000.
/*---------------------------------------------------------
Program: customerOrderTotal.p
Purpose: Display total order value for a customer
---------------------------------------------------------*/
DEFINE VARIABLE iCustNum AS INTEGER NO-UNDO.
DEFINE VARIABLE dTotalValue AS DECIMAL NO-UNDO.
/* Prompt user for customer number */
UPDATE iCustNum
LABEL "Enter Customer Number"
WITH FRAME fInput.
/* Find the customer */
FIND Customer
WHERE Customer.CustNum = iCustNum
NO-LOCK
NO-ERROR.
IF NOT AVAILABLE Customer THEN DO:
MESSAGE "Customer number" iCustNum "not found."
VIEW-AS ALERT-BOX ERROR.
RETURN.
END.
/* Calculate total order value */
FOR EACH Order
WHERE Order.CustNum = Customer.CustNum
NO-LOCK,
EACH OrderLine
WHERE OrderLine.OrderNum = Order.OrderNum
NO-LOCK:
dTotalValue = dTotalValue + OrderLine.ExtendedPrice.
END.
/* Display result */
MESSAGE
"Customer:" Customer.Name SKIP
"Total Order Value:" dTotalValue
VIEW-AS ALERT-BOX INFORMATION.
Nie jest żle bo program jest kompilowalny. Można kazać mu stosować nowy sposób deklaracji zmiennych i skrócony zapis relacji między tablicami i wtedy będzie jeszcze lepiej. Chodzi tu jednak o dobrze znana bazę. Co jeśli mamy własne bazy i bardziej skomplikowany problem? No i co na to Progress? Czy nie ma dla nas jakiegoś bardziej eleganckiego rozwiązania? Prędzej czy później o tym napiszę.