first commit
This commit is contained in:
639
README.md
Normal file
639
README.md
Normal file
@@ -0,0 +1,639 @@
|
||||
# DHBW 2025: Web Engineering Prüfungsleistung
|
||||
**Maximale Punktzahl: 100 Punkte**
|
||||
|
||||
---
|
||||
|
||||
## 1. Grundanforderungen (25 Punkte)
|
||||
|
||||
### Projektgrundlagen (15 Punkte)
|
||||
- **Funktionsfähige Anwendung** (8 Punkte)
|
||||
- Webapp oder Backend-Anwendung läuft fehlerfrei
|
||||
- Kernfunktionalitäten sind in der Projektdokumentation beschrieben und testbar
|
||||
- **Repository** (4 Punkte)
|
||||
- GitHub/GitLab/CodeBerg Repository ist (öffentlich) zugänglich
|
||||
- Commit-Historie zeigt kontinuierliche Entwicklung
|
||||
- **Projektdokumentation** (3 Punkte)
|
||||
- Aussagekräftiger Projekttitel und Beschreibung
|
||||
- README.md mit Installation, Verwendung und Technologie-Stack
|
||||
- Kurze Erklärung der Hauptfunktionen am Anfang relevanter Dateien
|
||||
|
||||
### Präsentation (20 Punkte)
|
||||
- **Elevator Pitch** (5 Punkte)
|
||||
- Maximal 1 Minute
|
||||
- Welches Problem wird gelöst und warum wurde es vorher noch nicht gelöst?
|
||||
- Zielgruppe definiert
|
||||
- **Technische Präsentation** (5 Punkte)
|
||||
- PowerPoint/Folien mit Technologie-Stack
|
||||
- Live-Demo der Anwendung
|
||||
- **Projektevolution:** (5 Punkte)
|
||||
Jedes Projekt verläuft unterschiedlich.
|
||||
Sofern es zutrifft, beschreibt die unterschiedlichen Phasen des Projekts.
|
||||
*Wie weit seid ihr gekommen? Wo waren die Stolpersteine? Wie könnte sich eure Anwendung bei fortgesetzter Arbeit entwickeln?*
|
||||
|
||||
0. (Paper) Prototype
|
||||
1. Make it work (PoC)
|
||||
2. Make it right (MvP)
|
||||
3. Make it fast (Production)
|
||||
|
||||
---
|
||||
|
||||
## 2. Clean Code Prinzipien (35 Punkte)
|
||||
|
||||
### Projektstruktur (10 Punkte)
|
||||
- **Ordnerstruktur** (5 Punkte)
|
||||
```
|
||||
projekt/
|
||||
├── src/
|
||||
│ ├── components/ # React Components / UI
|
||||
│ ├── config/ # Konfigurationen, Hard-coded Strings etc.
|
||||
│ ├── services/ # Separitiere für-sich-stehende Funktionen Klassen, Clients etc.
|
||||
│ ├── utils/ # Hilfsfunktionen
|
||||
│ ├── main.css # Haupteinstieg in CSS Klassen, Variablen, Imports etc.
|
||||
│ ├── main.js # Die eigentliche Appliktion
|
||||
│ └── index.js # Haupteinstieg in die gesamte Applikation
|
||||
├── public/ # Statische Assets, Ressourcen, Bilder etc.
|
||||
├── tests/ # Test-Dateien
|
||||
├── .gitignore
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
- Logische Trennung von Funktionalitäten
|
||||
- Konsistente Benennung
|
||||
|
||||
- **Datei-Organisation** (5 Punkte)
|
||||
- Ein Konzept pro Datei (Single Responsibility)
|
||||
- Konsistente Namenskonventionen: `kebab-case` für Dateien, `camelCase` für Variablen, `PascalCase` für Komponenten/Klassen
|
||||
|
||||
### Code-Qualität (15 Punkte)
|
||||
|
||||
#### KISS Prinzip (5 Punkte)
|
||||
|
||||
**❌ Nicht so einfach:**
|
||||
```javascript
|
||||
const processData = (d) => d.filter(x => x.status === 'active').map(x => ({...x, processed: true})).reduce((acc, curr) => acc + curr.value, 0);
|
||||
```
|
||||
|
||||
**✅ Einfach:**
|
||||
```javascript
|
||||
function calculateActiveTotal(data) {
|
||||
const activeItems = data.filter(item => item.status === 'active');
|
||||
const processedItems = activeItems.map(item => ({
|
||||
...item,
|
||||
processed: true
|
||||
}));
|
||||
return processedItems.reduce((total, item) => total + item.value, 0);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Nicht so einfach:**
|
||||
```javascript
|
||||
const u = users.find(u => u.id === id && u.active && !u.deleted) || null;
|
||||
```
|
||||
|
||||
**✅ Einfach:**
|
||||
```javascript
|
||||
function findActiveUser(users, userId) {
|
||||
return users.find(user =>
|
||||
user.id === userId &&
|
||||
user.active &&
|
||||
!user.deleted
|
||||
) || null;
|
||||
}
|
||||
```
|
||||
|
||||
#### Beschreibende Namen (5 Punkte)
|
||||
|
||||
**❌ Schlecht:**
|
||||
```javascript
|
||||
let isActive = true;
|
||||
let data = [];
|
||||
function calc(x, y) { return x * y; }
|
||||
```
|
||||
|
||||
**✅ Gut:**
|
||||
```javascript
|
||||
let isMenuVisible = true;
|
||||
let userProfiles = [];
|
||||
function calculateTotalPrice(price, taxRate) { return price * taxRate; }
|
||||
```
|
||||
|
||||
**❌ Unklar und uneindeutig:**
|
||||
```javascript
|
||||
const btn = document.getElementById('btn');
|
||||
let flag = false;
|
||||
function process(items) { /* ... */ }
|
||||
```
|
||||
|
||||
**✅ Sprechende Variablennamen:**
|
||||
```javascript
|
||||
const submitButton = document.getElementById('submit-btn');
|
||||
let isFormValid = false;
|
||||
function validateUserInput(formData) { /* ... */ }
|
||||
```
|
||||
|
||||
#### Funktionsverantwortlichkeiten (5 Punkte)
|
||||
|
||||
**❌ Zu viele Verantwortlichkeiten:**
|
||||
```javascript
|
||||
function handleUser(userData) {
|
||||
// Validiert Daten
|
||||
if (!userData.email) return false;
|
||||
// Speichert in DB
|
||||
database.save(userData);
|
||||
// Sendet Email
|
||||
sendWelcomeEmail(userData.email);
|
||||
// Updated UI
|
||||
updateUserList();
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Eine klare Verantwortlichkeit pro Funktion:**
|
||||
```javascript
|
||||
function validateUserData(userData) {
|
||||
return userData.email && userData.name;
|
||||
}
|
||||
|
||||
function saveUser(userData) {
|
||||
return database.save(userData);
|
||||
}
|
||||
|
||||
function sendWelcomeEmail(email) {
|
||||
return emailService.send(email, 'welcome');
|
||||
}
|
||||
```
|
||||
|
||||
### Kommentierung & Dokumentation (5 Punkte)
|
||||
**JSDoc ist optional und der beste Code muss nicht kommentiert werden.**
|
||||
|
||||
**❌ Über-/Unterkommentiert:**
|
||||
```javascript
|
||||
// Erhöht i um 1
|
||||
i++;
|
||||
// Checked ob user existiert
|
||||
if (user) {
|
||||
// Macht irgendwas
|
||||
doSomething();
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Sinnvoll kommentiert:**
|
||||
```javascript
|
||||
/**
|
||||
* Handles user authentication and session management
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* This file manages JWT tokens and user permissions
|
||||
*/
|
||||
|
||||
function calculateCompoundInterest(principal, rate, time) {
|
||||
// Using compound interest formula: A = P(1 + r/n)^(nt)
|
||||
return principal * Math.pow((1 + rate), time);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ Unterkommentiert:**
|
||||
```javascript
|
||||
function x(a,b,c,d,e) {
|
||||
return a*b+c-d/e;
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Mit JSDoc dokumentiert:**
|
||||
```javascript
|
||||
/**
|
||||
* Calculates shipping cost based on weight, distance and priority
|
||||
* @param {number} weight - Package weight in kg
|
||||
* @param {number} distance - Delivery distance in km
|
||||
* @param {boolean} isPriority - Priority delivery flag
|
||||
* @returns {number} Total shipping cost
|
||||
*/
|
||||
function calculateShippingCost(weight, distance, priority) {
|
||||
const baseCost = weight * 2.5;
|
||||
const distanceCost = distance * 0.1;
|
||||
const priorityMultiplier = priority ? 1.5 : 1;
|
||||
return (baseCost + distanceCost) * priorityMultiplier;
|
||||
}
|
||||
```
|
||||
|
||||
### Live-Demo (10 Punkte)
|
||||
- **Fehlerfreie Demonstration** (10 Punkte)
|
||||
- Anwendung läuft während der Präsentation stabil
|
||||
- Alle Kernfunktionen werden erfolgreich vorgeführt
|
||||
- Ungefähr 5 Minuten Demo-Zeit
|
||||
|
||||
---
|
||||
|
||||
## 3. Accessibility & UX (25 Punkte)
|
||||
|
||||
### Web Accessibility (15 Punkte)
|
||||
|
||||
#### Semantisches HTML (5 Punkte)
|
||||
|
||||
**❌ Nicht semantisch:**
|
||||
```html
|
||||
<div class="header">
|
||||
<div class="nav">
|
||||
<div class="nav-item">Home</div>
|
||||
<div class="nav-item">About</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="title">Welcome</div>
|
||||
<div class="text">Description...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**✅ Semantisch korrekt:**
|
||||
```html
|
||||
<header>
|
||||
<nav aria-label="Hauptnavigation">
|
||||
<ul>
|
||||
<li><a href="#home">Home</a></li>
|
||||
<li><a href="#about">About</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section>
|
||||
<h1>Welcome</h1>
|
||||
<p>Description...</p>
|
||||
</section>
|
||||
</main>
|
||||
```
|
||||
|
||||
#### ARIA Labels & Alt-Texte (5 Punkte)
|
||||
|
||||
**❌ Schlecht:**
|
||||
```html
|
||||
<img src="chart.png" alt="chart">
|
||||
<button onclick="closeMenu()">×</button>
|
||||
<input type="email" required>
|
||||
<div>We don't send spam</div>
|
||||
```
|
||||
|
||||
**✅ Gut:**
|
||||
```html
|
||||
<img src="chart.png" alt="Verkaufszahlen 2024: 40% Steigerung gegenüber Vorjahr">
|
||||
<button aria-label="Menü schließen" onclick="closeMenu()">×</button>
|
||||
<input type="email" aria-describedby="email-help" required>
|
||||
<div id="email-help">Wir versenden keine Spam-Mails</div>
|
||||
```
|
||||
|
||||
#### Tastaturnavigation (5 Punkte)
|
||||
- Alle interaktiven Elemente per Tab erreichbar
|
||||
- Sichtbarer Focus-Indikator
|
||||
- Logische Tab-Reihenfolge
|
||||
|
||||
**❌ Nicht tastaturzugänglich:**
|
||||
```html
|
||||
<div class="clickable">🍔</div>
|
||||
```
|
||||
|
||||
```css
|
||||
div.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Tastaturzugänglich:**
|
||||
```css
|
||||
div.clickable:focus {
|
||||
outline: 2px solid #007acc;
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
<div class="clickable" tabindex="0" aria-label="Menü öffnen">🍔</div>
|
||||
```
|
||||
|
||||
### Responsive Design (10 Punkte)
|
||||
|
||||
#### Mobile First Approach (5 Punkte - mindestens 1 Breakpoint)
|
||||
```css
|
||||
/* Mobile First */
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Flexbox/Grid Layout (5 Punkte)
|
||||
|
||||
**❌ Veraltete Layouts:**
|
||||
```css
|
||||
.container {
|
||||
float: left;
|
||||
width: 33.33%;
|
||||
}
|
||||
.clearfix::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Moderne Layouts:**
|
||||
```css
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.navigation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Erweiterte Technologien & Innovation (15 Punkte)
|
||||
|
||||
### Plattformunabhängigkeit (5 Punkte)
|
||||
- Funktioniert auf macOS, Windows, GNU/Linux
|
||||
- Browser-Kompatibilität (Chrome, Firefox, Safari, Edge)
|
||||
|
||||
**❌ Plattformspezifisch:**
|
||||
```javascript
|
||||
// Nur Chrome
|
||||
navigator.clipboard.writeText(text);
|
||||
```
|
||||
|
||||
**✅ Plattformunabhängig:**
|
||||
|
||||
In aller Regel sind solche sog. Polyfills kaum noch eigenhändig zu implementieren.
|
||||
Nichtsdestotrotz gilt es die Plattformunabhängig zu prüfen und ggf. Alternativen für die Implementierung zu finden wie im folgenden Beispiel für die Zwischenablage:
|
||||
|
||||
```javascript
|
||||
// Fallback für ältere Browser
|
||||
function copyToClipboard(text) {
|
||||
if (navigator.clipboard) {
|
||||
return navigator.clipboard.writeText(text);
|
||||
} else {
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Entwicklungs-Tools (10 Punkte)
|
||||
|
||||
#### Package Management (4 Punkte)
|
||||
|
||||
Sämtliche nodeJS/JavaScript Projekte sollten eine `package.json` beinhalten.
|
||||
`npm init` und ein Dialog öffnet sich.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"test": "jest",
|
||||
"lint": "eslint src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^4.0.0",
|
||||
"jest": "^29.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Build Tools & Optimierung (6 Punkte)
|
||||
- Vite, Webpack, oder ähnliche Tools
|
||||
- Optimierte Produktions-Builds
|
||||
- Debugger und unnötige console.log()'s entfernen
|
||||
- Code-Splitting
|
||||
- Nur Teile statt gesamte Bibliotheken importieren
|
||||
- Bspw. PostCSS für Entfernung von überschüssigem CSS
|
||||
|
||||
**❌ Entwicklungs-Build:**
|
||||
```javascript
|
||||
// Unminified, alle Dependencies geladen
|
||||
import * as _ from 'lodash';
|
||||
// Debugging
|
||||
console.log('Debug info:', data);
|
||||
debugger;
|
||||
```
|
||||
|
||||
**✅ Produktions-Build:**
|
||||
```javascript
|
||||
// Minified, nur benötigte Funktionen
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
// Debug-Code entfernt in Production
|
||||
```
|
||||
|
||||
### Zusätzliche Features (Bonus: bis zu 10 Punkte)
|
||||
**Wähle mindestens 2 Bereiche:**
|
||||
|
||||
#### API Integration (5 Punkte)
|
||||
|
||||
**❌ Ohne Error Handling:**
|
||||
```javascript
|
||||
async function getUser(id) {
|
||||
const response = await fetch(`/api/users/${id}`);
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Mit Error Handling:**
|
||||
```javascript
|
||||
async function fetchUserData(userId) {
|
||||
try {
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Benutzerdaten:', error);
|
||||
throw new Error('Benutzerdaten konnten nicht geladen werden');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Local Storage / State Management (5 Punkte)
|
||||
|
||||
**❌ Ohne Error Handling:**
|
||||
```javascript
|
||||
function saveData(data) {
|
||||
localStorage.setItem('data', JSON.stringify(data));
|
||||
}
|
||||
function loadData() {
|
||||
return JSON.parse(localStorage.getItem('data'));
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Mit Error Handling:**
|
||||
```javascript
|
||||
function saveUserPreferences(prefs) {
|
||||
try {
|
||||
localStorage.setItem('userPrefs', JSON.stringify(prefs));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('Local Storage nicht verfügbar:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadUserPreferences() {
|
||||
try {
|
||||
const data = localStorage.getItem('userPrefs');
|
||||
return data ? JSON.parse(data) : getDefaultPreferences();
|
||||
} catch (error) {
|
||||
console.warn('Fehler beim Laden der Einstellungen:', error);
|
||||
return getDefaultPreferences();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Progressive Web App Features (5 Punkte)
|
||||
- Service Worker für Offline-Funktionalität
|
||||
- Manifest.json für App-Installation
|
||||
- Caching-Strategien
|
||||
|
||||
**❌ Ohne PWA:**
|
||||
```html
|
||||
<!-- Nur normale Website -->
|
||||
```
|
||||
|
||||
**✅ Mit PWA Features:**
|
||||
```html
|
||||
<!-- manifest.json -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<!-- Service Worker -->
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### Testing (10 Punkte)
|
||||
|
||||
Von Vornherein mitgedachte Tests helfen beim Code-Splitting.
|
||||
Um beim Testen jene Funktionen eindeutig referenzieren zu können, hilft es sie in einzelne Dateien zu verteilen und Ordnern zu gruppieren.
|
||||
|
||||
**❌ Ohne Tests:**
|
||||
```javascript
|
||||
// Keine Tests vorhanden
|
||||
function calculateTotal(price, tax) {
|
||||
return price + (price * tax);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Mit Tests:**
|
||||
```javascript
|
||||
// calculate.js
|
||||
export function calculateTotal(price, tax) {
|
||||
if (typeof price !== 'number' || typeof tax !== 'number') {
|
||||
throw new Error('Price and tax must be numbers');
|
||||
}
|
||||
return price + (price * tax);
|
||||
}
|
||||
|
||||
// calculate.test.js
|
||||
import { calculateTotal } from './calculate.js';
|
||||
|
||||
test('calculateTotal should return correct sum', () => {
|
||||
expect(calculateTotal(100, 0.19)).toBeCloseTo(119);
|
||||
});
|
||||
|
||||
test('calculateTotal should throw error for invalid input', () => {
|
||||
expect(() => calculateTotal('100', 0.19)).toThrow();
|
||||
});
|
||||
```
|
||||
|
||||
#### Responsive Design Erweitert (5 Punkte - 2 oder mehr Breakpoints)
|
||||
```css
|
||||
/* Mobile */
|
||||
.grid { display: block; }
|
||||
|
||||
/* Tablet */
|
||||
@media (min-width: 768px) {
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop */
|
||||
@media (min-width: 1024px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Large Desktop */
|
||||
@media (min-width: 1440px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Security Features (5 Punkte)
|
||||
|
||||
**❌ Unsicher:**
|
||||
```javascript
|
||||
// XSS anfällig
|
||||
element.innerHTML = userInput;
|
||||
// Unvalidierte Eingabe
|
||||
function search(query) {
|
||||
return database.query(`SELECT * FROM users WHERE name = '${query}'`);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ Sicher:**
|
||||
```javascript
|
||||
// XSS-sicher durch HTML-Escaping
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
element.innerHTML = escapeHtml(userInput);
|
||||
|
||||
// SQL-Injection sicher
|
||||
function search(query) {
|
||||
const sanitizedQuery = query.replace(/[^\w\s]/gi, '');
|
||||
return database.query('SELECT * FROM users WHERE name = ?', [sanitizedQuery]);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Abgabe
|
||||
|
||||
- **Deadline:** 20.07.2025
|
||||
- **Format:** GitHub/GitLab Repository Link + Live-Demo URL (falls gehostet)
|
||||
- **Präsentation:** 11.07.2025 - 10-15 Minuten pro Gruppe + ~5 Minuten Demo
|
||||
|
||||
## Tipps
|
||||
|
||||
1. **Beginnt früh** mit der Eingrenzung des Funktionsumfangs
|
||||
2. **Testet eure Anwendung** auf verschiedenen Geräten, Betriebssystemen und Browsern
|
||||
3. **Fragt bei Unklarheiten** rechtzeitig nach
|
||||
4. **Fokussiert euch** auf Qualität statt Quantität
|
||||
5. **Behaltet Barrieren im Blick** und vermeidet sie rechtzeitig
|
||||
5. **Verwendet gute Struktur** um eure Code-Beispiele zu organisieren
|
||||
Reference in New Issue
Block a user