832401113 Software Engineering Practice Assignment 1: A Frontend–Backend Separated Calculator

2501_92597618 2501_92597618 墨衍会员 · AI 创作全网分发 墨衍智能分发 通过墨衍一键同步至各平台 可同步平台 CSDN微信公众号微博知乎掘金百家号博客园抖音小红书 你的文章也可以这样分发 墨衍会员 ¥399起/年,写一次发全网 我也要 + 加入社区

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

ResourceLink
Front end repositoryChrisKslna/calculator-frontend
Back end repositoryChrisKslna/calculator-backend
Front end coding conventionsFront end codestyle.md
Back end coding conventionsBack end codestyle.md
Public websiteOnline 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.

RequirementImplementation
Addition, subtraction, multiplication, and divisionThe back end calculates; the front end displays the returned result
Compound expressionsOperator precedence, parentheses, decimals, and unary plus and minus
Error handlingMessages for invalid expressions, division by zero, and connection failures
Calculation historySuccessful calculations are stored with their expression, result, and timestamp
Deletion of a selected recordDelete through the API using the record ID, then reload the list
Front end and back end separationHTTP and JSON communication; the browser does not evaluate expressions
Additional featuresHistory pagination, expression reuse, keyboard input, and a narrow screen layout

2 PSP Time Record

ActivityPlanned timeActual timeStatus
Read requirements and check the environment2025Completed
Design the architecture, API, and database3035Completed
Initialize the project and implement the first endpoint3030Completed
Implement expression parsing and calculation90100Completed
Implement the database and history API6065Completed
Build the front end and interactions90110Completed, including two rounds of interface simplification
Integrate the front end and back end4550Verified locally
Test and fix issues4560Verified locally
Deploy and verify public access6060Completed
Write the blog and capture demonstrations6090Article and final interface screenshots prepared
Upload to GitHub, review materials, and reflect3030Source published
Total560655

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.

img

Addition

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

img

Subtraction

Entering 8-3 returns 5.

img

Multiplication

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

img

Division

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

img

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.

img

Operator Precedence

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

img

Parentheses

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

img

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.

img

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.

img

Division by Zero

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

img

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.

img

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.

img

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.

img

Deletion Confirmation

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

img

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.

img

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.

img

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.

img

4 System Design

Overall Architecture

img

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

img

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

MethodPathInputResponse
GET/api/healthNoneService and database health
POST/api/calculateJSON expression fieldResult and saved record
GET/api/historypage, page_sizeRecords, total count, page count, and current page
DELETE/api/history/{id}Record IDDeleted 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.

FieldTypePurpose
idINTEGER PRIMARY KEY AUTOINCREMENTUnique record ID used for deletion
expressionTEXT NOT NULLInput expression with leading and trailing whitespace removed
resultTEXT NOT NULLDecimal result string generated by the back end
created_atTEXT NOT NULLISO 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

  1. The user enters an expression and clicks equals or presses Enter.
  2. The front end sends a POST request containing the expression field.
  3. The back end validates, parses, and evaluates the expression.
  4. On success, it saves the expression, result, and timestamp in the database.
  5. After the transaction commits, it returns the result and record details.
  6. 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.

CheckResult
Arithmetic, parentheses, precedence, decimals, and unary signsPassed
Invalid input, division by zero, length and nesting limitsPassed
JSON format, content type, status codes, and pagination parametersPassed
Ignoring a forged client result fieldPassed
No history for failed calculations; no success response after a save failurePassed
Deletion by ID, persistence after application recreation, and database isolationPassed
Browser Enter key, history pagination, and expression reusePassed
Deletion dialog and cancellationPassed
Actual deletion through the APIRecord count changed from 8 to 7; ID 2 was removed
Stopping and restarting the demonstration back endCalculation 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.

...全文

相关推荐

评论
收藏 已收藏
分享
返回顶部