832401113 Software Engineering Practice Assignment 1: A Frontend–Backend Separated Calculator
Assignment 1 Design and Implementation of a Calculator with Front End and Back End Separation
Name: HuangZijun | Student ID: 832401113
This project is the first assignment for the Software Engineering course. The assignment requirements include building a calculator with separate front-end and back-end components, supporting arithmetic expressions, and storing and deleting calculation history in a database. The goal is to understand how the interface, server, and database work together, and gain practical experience in testing and deployment.
Repositories and Website
| Resource | Link |
|---|---|
| Front end repository | ChrisKslna/calculator-frontend |
| Back end repository | ChrisKslna/calculator-backend |
| Front end coding conventions | Front end codestyle.md |
| Back end coding conventions | Back end codestyle.md |
| Public website | Online Calculator |
Table of Contents
目录
- Assignment 1 Design and Implementation of a Calculator with Front End and Back End Separation
- Repositories and Website
- Table of Contents
- 1 Objectives and Scope
- 2 PSP Time Record
- 3 Final Product and Demonstration
- Main Interface
- Addition
- Subtraction
- Multiplication
- Division
- Decimals
- Operator Precedence
- Parentheses
- Unary Minus
- Invalid Expression
- Division by Zero
- History After a Page Refresh
- History Pagination
- Reusing an Expression
- Deletion Confirmation
- Reloading History After Deletion
- Stopping the Back End
- Restarting the Back End
- 4 System Design
- Overall Architecture
- Functional Structure
- Project Organization
- Front End Design
- API Design
- Database Design
- Expression Parsing
- Request Flow for a Calculation
- 5 Key Code
- Sending the Expression
- Calculating and Saving on the Back End
- Enforcing Operator Precedence
- Querying and Deleting History
- 6 Test Results
- 7 Personal Journey and Learnings
1 Objectives and Scope
This project is a web calculator. The user enters an expression in the browser, and the front end sends it to the back end through an HTTP API. The back end validates the input, parses the expression, calculates the result, and saves successful calculations in SQLite. The front end then displays the response and refreshes the history list.
| Requirement | Implementation |
|---|---|
| Addition, subtraction, multiplication, and division | The back end calculates; the front end displays the returned result |
| Compound expressions | Operator precedence, parentheses, decimals, and unary plus and minus |
| Error handling | Messages for invalid expressions, division by zero, and connection failures |
| Calculation history | Successful calculations are stored with their expression, result, and timestamp |
| Deletion of a selected record | Delete through the API using the record ID, then reload the list |
| Front end and back end separation | HTTP and JSON communication; the browser does not evaluate expressions |
| Additional features | History pagination, expression reuse, keyboard input, and a narrow screen layout |
2 PSP Time Record
| Activity | Planned time | Actual time | Status |
|---|---|---|---|
| Read requirements and check the environment | 20 | 25 | Completed |
| Design the architecture, API, and database | 30 | 35 | Completed |
| Initialize the project and implement the first endpoint | 30 | 30 | Completed |
| Implement expression parsing and calculation | 90 | 100 | Completed |
| Implement the database and history API | 60 | 65 | Completed |
| Build the front end and interactions | 90 | 110 | Completed, including two rounds of interface simplification |
| Integrate the front end and back end | 45 | 50 | Verified locally |
| Test and fix issues | 45 | 60 | Verified locally |
| Deploy and verify public access | 60 | 60 | Completed |
| Write the blog and capture demonstrations | 60 | 90 | Article and final interface screenshots prepared |
| Upload to GitHub, review materials, and reflect | 30 | 30 | Source published |
| Total | 560 | 655 |
3 Final Product and Demonstration
Main Interface
The page has a white background, gray buttons, and simple borders. The calculator is on the left and the history list is on the right.

Addition
Entering 12+8 returns 20. The history list also shows the expression, result, and timestamp.

Subtraction
Entering 8-3 returns 5.

Multiplication
Entering 6*7 returns 42. The keypad displays ×, and the back end also accepts * typed from the keyboard.

Division
Entering 9/4 returns 2.25. The result is not truncated to an integer.

Decimals
Entering 0.1+0.2 returns 0.3. Number tokens are converted directly from text to Decimal, and the result is returned as a decimal string.

Operator Precedence
Entering 1+2*3 returns 7 because multiplication is evaluated before addition.

Parentheses
Entering (1+2)*3 returns 9 because the addition inside the parentheses is evaluated first.

Unary Minus
Entering 3*-2 returns -6. The parser treats the minus sign before the second number as a unary operator. Unary plus is also supported, as in +5.

Invalid Expression
Entering 1+ produces a message explaining that an operand is missing. The result display changes to a dash. The history count remains 8 because an invalid calculation is not saved.

Division by Zero
Entering 1/0 produces a message meaning "The divisor cannot be zero." No new history record is created.

History After a Page Refresh
After the page is refreshed, the input area is reset, but the history still contains 8 records. The front end requests the list again from the back end; the records are not held only in the current page's memory.

History Pagination
The page displays 5 records at a time. The 8 records occupy two pages, with the 3 older records on the second page.

Reusing an Expression
Clicking the history expression 8-3 copies it into the input field. This action does not calculate immediately. A new request is sent only after the user clicks the equals button or presses Enter.

Deletion Confirmation
Clicking Delete for a record opens a dialog showing its expression and result, with Cancel and Confirm buttons.

Reloading History After Deletion
The same back end deletion endpoint was then called to remove demonstration record ID 2, which contained 8-3=5. After refreshing, the record count fell from 8 to 7, leaving two records on the second page.

Stopping the Back End
With the front end still running, the back end service was stopped and 2+2 was submitted. The page displayed a connection error and could not produce a new result. This check confirmed that the browser depends on the back end for calculation.

Restarting the Back End
After the back end was restarted with the same database file, the refreshed page still showed 7 records. Deleted record ID 2 did not return. Both successful calculations and deletion changes persisted across the restart.

4 System Design
Overall Architecture

The front end and back end have separate source directories and repositories. The front end collects input, sends requests, and updates the page. The Flask API calls the calculation and database modules. SQLite stores calculation history. Requests carry expressions; the back end calculates and stores the results before returning JSON.
For local development, serve.py serves static files and forwards /api/ requests to the separate back end. This gives the browser one entry point without hardcoding the back end address in JavaScript. The forwarding service does not calculate expressions. The public deployment uses Nginx and a dedicated /chriskslna/ path on the shared server.
Functional Structure

Project Organization
calculator-frontend/
├── src/
│ ├── index.html Page structure
│ ├── styles.css Page and narrow screen styles
│ └── app.js Interactions and HTTP requests
├── serve.py Local static server and API forwarding
├── README.md
└── codestyle.md
calculator-backend/
├── src/
│ ├── app.py Flask endpoints and error responses
│ ├── calculator.py Expression parsing and calculation
│ └── database.py History database operations
├── tests/
│ ├── test_calculator.py
│ └── test_api.py
├── requirements.txt
├── README.md
└── codestyle.md
Front End Design
HTML defines the expression field, result display, keypad, history list, and deletion dialog. CSS uses two columns on wide screens and switches to one column at widths of 680 pixels or less. JavaScript handles editing, button events, and network requests. It does not use eval or Function to calculate expressions.
While a calculation is pending, the expression field becomes read-only and the keypad is disabled to prevent duplicate submissions. The result display uses the string returned by the back end. Editing the expression changes the existing result label to "Previous result," helping distinguish it from an answer to the new expression. An error replaces the result with a dash and displays a message.
History expressions and results are inserted using textContent, so they are not interpreted as HTML. A successful calculation reloads the first history page. Deletion also triggers a new query, keeping the display consistent with the database.
The API base path is derived from the page directory with new URL('./api', document.baseURI).pathname. Local root pages therefore use /api, while the public page uses /chriskslna/api. This keeps the calculator's requests separate from the classmate's root website.
API Design
| Method | Path | Input | Response |
|---|---|---|---|
| GET | /api/health | None | Service and database health |
| POST | /api/calculate | JSON expression field | Result and saved record |
| GET | /api/history | page, page_size | Records, total count, page count, and current page |
| DELETE | /api/history/{id} | Record ID | Deleted ID |
The front end requests page_size=5. The API default is 8, and the permitted range is 1 to 100. Records are sorted by descending ID. If the last record on the final page is deleted, the next history query adjusts the requested page number to the last available page.
Example calculation request:
{"expression":"(1+2)*3"}
A successful response contains success, id, expression, result, and created_at. The result field is a string such as "9", so the front end does not need to convert it to a number.
A failure response contains success: false, an error code, and a message. Some expression errors also include a one-based position. For 1/0, the code is DIVISION_BY_ZERO and the position is 2. The current API returns its message in Chinese, meaning "The divisor cannot be zero."
Expression and JSON errors return HTTP 400. Other responses include 404 for a missing record, 405 for an unsupported method, 413 for a request larger than 16 KiB, 415 for a non-JSON content type, and 503 for a database error. Successful operations return 200.
Database Design
SQLite stores the records in a table named calculation_history.
| Field | Type | Purpose |
|---|---|---|
| id | INTEGER PRIMARY KEY AUTOINCREMENT | Unique record ID used for deletion |
| expression | TEXT NOT NULL | Input expression with leading and trailing whitespace removed |
| result | TEXT NOT NULL | Decimal result string generated by the back end |
| created_at | TEXT NOT NULL | ISO 8601 timestamp with the UTC time zone |
The result is stored as TEXT to avoid converting it to a binary floating point value. The back end generates the timestamp, and the browser displays it in its local time zone. CREATE TABLE IF NOT EXISTS initializes the table without clearing existing data.
Saving, querying, and deleting are all performed by the back end. SQL statements use parameter placeholders. Each operation opens a connection, commits or rolls back its transaction, and closes the connection. Python's SQLite connection context manager handles transactions but does not automatically close the connection, so this project uses close() in a finally block. Python SQLite documentation
The default database file is instance/history.db inside the back end directory. CALCULATOR_DATABASE can specify another location. There are no user accounts, so visitors to the same deployment share its history. Two calculator projects can use separate database files.
Expression Parsing
The calculation module implements tokenization and recursive descent parsing. It does not use a third-party expression evaluator or execute user input as program code.
The tokenizer first normalizes ×, ÷, and − to *, /, and -. It then separates the input into numbers, operators, and parentheses. Whitespace is skipped, and unsupported characters cause an error. The parser has four levels:
expression Addition and subtraction
↓
term Multiplication and division
↓
unary Unary plus and minus
↓
primary A number or a parenthesized expression
The expression method calls term before applying addition or subtraction. This makes multiplication happen first in 1+2*3. When primary encounters an opening parenthesis, it calls expression again to evaluate the enclosed content. Operators at the same binary precedence level are processed from left to right. Unary minus can appear after multiplication or division, as in 3*-2.
Number strings are converted to Decimal. Calculations use 28 significant digits and ROUND_HALF_EVEN rounding. This produces 0.3 for 0.1+0.2, but repeating decimals such as 1/3 are still limited by the configured precision. Python Decimal documentation
Input is limited to 500 characters and 50 levels of parentheses. Scientific notation, powers, variables, functions, and implicit multiplication are not supported. For example, 2(3+4) must be written as 2*(3+4).
Request Flow for a Calculation
- The user enters an expression and clicks equals or presses Enter.
- The front end sends a POST request containing the
expressionfield. - The back end validates, parses, and evaluates the expression.
- On success, it saves the expression, result, and timestamp in the database.
- After the transaction commits, it returns the result and record details.
- The front end displays the result and requests the updated history list.
An invalid expression or division by zero stops the flow before a history record is created. If saving fails, the API returns a service error rather than reporting that the calculation was successfully saved.
5 Key Code
Sending the Expression
const data = await api('/calculate', {
method: 'POST',
body: JSON.stringify({ expression: expressionInput.value }),
});
resultOutput.textContent = data.result;
await loadHistory(1);
This excerpt shows the core calculation request. The front end sends the expression and uses the returned result. The api helper handles timeouts, HTTP status codes, and JSON responses. History is updated through a subsequent request.
Calculating and Saving on the Back End
expression = payload.get("expression")
result = calculate_expression(expression)
record = database.save_calculation(
app.config["DATABASE"], expression.strip(), result
)
return {"success": True, **record}
This code connects the API, calculation, and storage layers. A client-supplied result field is ignored: the calculation module determines the answer. The success response is constructed only after the database operation completes.
Enforcing Operator Precedence
def expression(self):
result = self.term()
while self.current.kind in ("+", "-"):
operator = self.current.kind
self.index += 1
right = self.term()
result = result + right if operator == "+" else result - right
return result
Both operands of addition or subtraction are obtained through term(), which handles multiplication and division first. The loop processes operators in sequence, so 8-3-2 is evaluated from left to right and returns 3.
Querying and Deleting History
rows = connection.execute(
"SELECT id, expression, result, created_at "
"FROM calculation_history ORDER BY id DESC LIMIT ? OFFSET ?",
(page_size, (page - 1) * page_size),
).fetchall()
cursor = connection.execute(
"DELETE FROM calculation_history WHERE id = ?", (record_id,)
)
return cursor.rowcount == 1
LIMIT and OFFSET implement pagination. Deletion uses the record ID, and rowcount indicates whether a row was actually removed. If the record does not exist, the API returns 404. The front end reloads the list after deletion instead of merely removing a visual element.
6 Test Results
Local verification was repeated using Python 3.12.6, Flask 3.1.3, and Waitress 3.0.2.
| Check | Result |
|---|---|
| Arithmetic, parentheses, precedence, decimals, and unary signs | Passed |
| Invalid input, division by zero, length and nesting limits | Passed |
| JSON format, content type, status codes, and pagination parameters | Passed |
Ignoring a forged client result field | Passed |
| No history for failed calculations; no success response after a save failure | Passed |
| Deletion by ID, persistence after application recreation, and database isolation | Passed |
| Browser Enter key, history pagination, and expression reuse | Passed |
| Deletion dialog and cancellation | Passed |
| Actual deletion through the API | Record count changed from 8 to 7; ID 2 was removed |
| Stopping and restarting the demonstration back end | Calculation failed while stopped; 7 records remained after restart |
7 Personal Journey and Learnings
Through this assignment, I gained a clearer understanding of front-end and back-end separation. I learned how the browser sends an expression to the server and displays the returned result. Using SQLite also helped me understand how calculation history is saved and retrieved.
Expression parsing required attention to operator precedence and parentheses. Testing expressions such as 1+2*3 and (1+2)*3 helped me understand these rules. Checking invalid input, division by zero, and history after a restart also showed me why testing should cover more than successful calculations.
One practical difficulty was deployment: the chosen port could not be accessed from the internet. This was resolved by using a separate URL path on the shared server. The experience helped me connect classroom concepts with a working application. In the future, I would like to add history search and separate histories for different users.
相关推荐
科技中介服务机构如何利用平台提升服务质量与客户粘性?.docx
科技中介服务机构如何利用平台提升服务质量与客户粘性?
(共129页PPT)一个鸡蛋品某省市定位农业品牌小鲜蛋战略创意报告全案某著名企业.pptx
(共129页PPT)一个鸡蛋品某省市定位农业品牌小鲜蛋战略创意报告全案某著名企业.pptx
高校技术转移办公室人员如何系统化管理院所的科技成果转化资源?.docx
科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。
科技中介服务机构如何提升技术需求挖掘的准确性?.docx
科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。
空心杯电机:机器人驱动全球精密微型运动新增长.docx
空心杯电机:机器人驱动全球精密微型运动新增长


