Skip to content

Funktionen

Funktionsargumente (idealerweise 2 oder weniger)

Die Begrenzung der Anzahl von Funktionsparametern ist äußerst wichtig, weil es das Testen der Funktion erleichtert. Mehr als drei Parameter führen zu einer kombinatorischen Explosion, bei der zahlreiche Testfälle für jede Argumentkombination benötigt werden.

Ein oder zwei Argumente sind ideal, drei sollten möglichst vermieden werden. Bei mehreren Argumenten sollten diese in einem Objekt gebündelt werden. Grundsätzlich deutet eine höhere Parameteranzahl oft darauf hin, dass die Funktion zu viele Aufgaben erfüllt. In Ausnahmefällen genügt meist ein höherwertiges Objekt als Parameter.

Da JavaScript die dynamische Erzeugung von Objekten ohne umfangreiche Klassendeklarationen ermöglicht, können Sie bei hohem Parameterbedarf ein Objekt verwenden.

Um die erwarteten Eigenschaften klar zu definieren, bietet sich die ES2015/ES6-Destrukturierungssyntax an. Dies hat mehrere Vorteile:

  1. Die benötigten Eigenschaften sind direkt an der Funktionssignatur erkennbar.
  2. Sie ermöglicht die Simulation benannter Parameter.
  3. Die Destrukturierung klont zudem die angegebenen primitiven Werte des übergebenen Objekts, was Nebeneffekte reduziert. Hinweis: Destrukturierte Objekte und Arrays werden NICHT geklont.
  4. Linter können vor ungenutzten Eigenschaften warnen – ohne Destrukturierung unmöglich.

Schlecht:

javascript
function createMenu(title, body, buttonText, cancellable) {
  // ...
}

createMenu("Foo", "Bar", "Baz", true);

Gut:

javascript
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}

createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});

Funktionen sollten eine einzige Aufgabe erfüllen

Dies ist die wichtigste Regel in der Softwareentwicklung. Funktionen mit mehreren Aufgaben sind schwerer kombinierbar, testbar und nachvollziehbar. Fokussierte Funktionen lassen sich leichter refaktorieren und verbessern die Lesbarkeit. Wenn Sie nur eine Regel beachten, sollte es diese sein.

Schlecht:

javascript
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

Gut:

javascript
function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

Funktionsnamen sollten ihre Funktion klar benennen

Schlecht:

javascript
function addToDate(date, month) {
  // ...
}

const date = new Date();

// It's hard to tell from the function name what is added
addToDate(date, 1);

Gut:

javascript
function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

Funktionen sollten nur eine Abstraktionsebene haben

Mehrere Abstraktionsebenen deuten auf überladene Funktionen hin. Getrennte Funktionen fördern Wiederverwendbarkeit und Testbarkeit.

Schlecht:

javascript
function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      // ...
    });
  });

  const ast = [];
  tokens.forEach(token => {
    // lex...
  });

  ast.forEach(node => {
    // parse...
  });
}

Gut:

javascript
function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const syntaxTree = parse(tokens);
  syntaxTree.forEach(node => {
    // parse...
  });
}

function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(" ");
  const tokens = [];
  REGEXES.forEach(REGEX => {
    statements.forEach(statement => {
      tokens.push(/* ... */);
    });
  });

  return tokens;
}

function parse(tokens) {
  const syntaxTree = [];
  tokens.forEach(token => {
    syntaxTree.push(/* ... */);
  });

  return syntaxTree;
}

Doppelten Code vermeiden

Redundanzen sind kritisch zu vermeiden, da Änderungen an mehreren Stellen vorgenommen werden müssten.

Stellen Sie sich ein Restaurant-Inventarsystem vor: Mehrere Tomaten-Listen erfordern synchronisierte Updates. Eine einzige Liste vereinfacht dies.

Redundanz entsteht oft bei ähnlichen Funktionen mit leichten Unterschieden, deren Unterschiede es erfordern, zwei oder mehr separate Funktionen zu erstellen, die viele gleiche Aufgaben erfüllen. Eine abstrakte Lösung kann dies konsolidieren.

Gute Abstraktionen erfordern die Einhaltung der SOLID-Prinzipien (siehe Klassen-Abschnitt). Schlechte Abstraktionen sind schlimmer als doppelter Code, also seien Sie vorsichtig! Bei gelungener Abstraktion jedoch: Implementieren! So vermeiden Sie Multiplikation von Änderungspunkten.

Schlecht:

javascript
function showDeveloperList(developers) {
  developers.forEach(developer => {
    const expectedSalary = developer.calculateExpectedSalary();
    const experience = developer.getExperience();
    const githubLink = developer.getGithubLink();
    const data = {
      expectedSalary,
      experience,
      githubLink
    };

    render(data);
  });
}

function showManagerList(managers) {
  managers.forEach(manager => {
    const expectedSalary = manager.calculateExpectedSalary();
    const experience = manager.getExperience();
    const portfolio = manager.getMBAProjects();
    const data = {
      expectedSalary,
      experience,
      portfolio
    };

    render(data);
  });
}

Gut:

javascript
function showEmployeeList(employees) {
  employees.forEach(employee => {
    const expectedSalary = employee.calculateExpectedSalary();
    const experience = employee.getExperience();

    const data = {
      expectedSalary,
      experience
    };

    switch (employee.type) {
      case "manager":
        data.portfolio = employee.getMBAProjects();
        break;
      case "developer":
        data.githubLink = employee.getGithubLink();
        break;
    }

    render(data);
  });
}

Standardobjekte mit Object.assign setzen

Schlecht:

javascript
const menuConfig = {
  title: null,
  body: "Bar",
  buttonText: null,
  cancellable: true
};

function createMenu(config) {
  config.title = config.title || "Foo";
  config.body = config.body || "Bar";
  config.buttonText = config.buttonText || "Baz";
  config.cancellable =
    config.cancellable !== undefined ? config.cancellable : true;
}

createMenu(menuConfig);

Gut:

javascript
const menuConfig = {
  title: "Order",
  // User did not include 'body' key
  buttonText: "Send",
  cancellable: true
};

function createMenu(config) {
  let finalConfig = Object.assign(
    {
      title: "Foo",
      body: "Bar",
      buttonText: "Baz",
      cancellable: true
    },
    config
  );
  return finalConfig
  // config now equals: {title: "Order", body: "Bar", buttonText: "Send", cancellable: true}
  // ...
}

createMenu(menuConfig);

Flags nicht als Funktionsparameter verwenden

Flags implizieren Mehrfachfunktionalität. Trennen Sie solche Funktionen in spezialisierte Varianten auf.

Schlecht:

javascript
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

Gut:

javascript
function createFile(name) {
  fs.create(name);
}

function createTempFile(name) {
  createFile(`./temp/${name}`);
}

Nebeneffekte vermeiden (Teil 1)

Nebeneffekte umfassen mehr als Input-Output-Operationen: Dateizugriffe, Modifikation globaler Variablen oder unerwünschte Zustandsänderungen.

Nebeneffekte sind manchmal nötig (z.B. Dateischreiben), sollten aber zentralisiert werden. Ein einziger Service pro Nebeneffekt-Typ verhindert Chaos.

Vermeiden Sie geteilten Zustand ohne Struktur, veränderbare Datentypen mit unkontrolliertem Zugriff und dezentrale Nebeneffekt-Quellen.

Schlecht:

javascript
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
let name = "Ryan McDermott";

function splitIntoFirstAndLastName() {
  name = name.split(" ");
}

splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Gut:

javascript
function splitIntoFirstAndLastName(name) {
  return name.split(" ");
}

const name = "Ryan McDermott";
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

Nebeneffekte vermeiden (Teil 2)

JavaScript-Objekte und Arrays sind veränderbar. Bei der Übergabe als Parameter ist Vorsicht geboten, da unerwartete Seiteneffekte entstehen können.

Beispiel Warenkorb-Array: Eine Modifikation durch eine Funktion betrifft alle Nutzer dieses Arrays. Problemfall: Netzwerk-Wiederholungsversuch während gleichzeitiger Benutzerinteraktion.

Wenn der Nutzer versehentlich einen Artikel hinzufügt, bevor die Netzwerkanfrage beginnt, wird der modifizierte „cart“-Array gesendet, da die Änderung direkt vorgenommen wurde.

Lösung: „addItemToCart“ sollte den Warenkorb immer klonen, modifizieren und den Klon zurückgeben. So bleibt der Originalzustand erhalten.

Zwei Einschränkungen:

    1. Direkte Objektmodifikationen sind selten notwendig und meist vermeidbar.
    1. Tiefe Klone großer Objekte sind performancerelevant. Bibliotheken wie Immutable.js optimieren dies.

Schlecht:

javascript
const addItemToCart = (cart, item) => {
  cart.push({ item, date: Date.now() });
};

Gut:

javascript
const addItemToCart = (cart, item) => {
  return [...cart, { item, date: Date.now() }];
};

Globale Funktionen nicht manipulieren

Globale Erweiterungen (z.B. „Array.prototype.diff“) können zu Konflikten mit anderen Bibliotheken führen. Besser: ES2015/ES6-Klassen nutzen.

Schlecht:

javascript
Array.prototype.diff = function diff(comparisonArray) {
  const hash = new Set(comparisonArray);
  return this.filter(elem => !hash.has(elem));
};

Gut:

javascript
class SuperArray extends Array {
  diff(comparisonArray) {
    const hash = new Set(comparisonArray);
    return this.filter(elem => !hash.has(elem));
  }
}

Funktionale Programmierung vorziehen

JavaScript ermöglicht funktionalen Programmierstil, der saubereren und testbareren Code fördert.

Schlecht:

javascript
const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

let totalOutput = 0;

for (let i = 0; i < programmerOutput.length; i++) {
  totalOutput += programmerOutput[i].linesOfCode;
}

Gut:

javascript
const programmerOutput = [
  {
    name: "Uncle Bobby",
    linesOfCode: 500
  },
  {
    name: "Suzie Q",
    linesOfCode: 1500
  },
  {
    name: "Jimmy Gosling",
    linesOfCode: 150
  },
  {
    name: "Gracie Hopper",
    linesOfCode: 1000
  }
];

const totalOutput = programmerOutput.reduce(
  (totalLines, output) => totalLines + output.linesOfCode,
  0
);

Bedingungen kapseln

Schlecht:

javascript
if (fsm.state === "fetching" && isEmpty(listNode)) {
  // ...
}

Gut:

javascript
function shouldShowSpinner(fsm, listNode) {
  return fsm.state === "fetching" && isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}

Negative Bedingungen vermeiden

Schlecht:

javascript
function isDOMNodeNotPresent(node) {
  // ...
}

if (!isDOMNodeNotPresent(node)) {
  // ...
}

Gut:

javascript
function isDOMNodePresent(node) {
  // ...
}

if (isDOMNodePresent(node)) {
  // ...
}

Bedingungen vermeiden

Polymorphie kann viele „if“-Anweisungen ersetzen. Funktionen sollten nur eine Aufgabe erfüllen – Bedingungen widersprechen diesem Prinzip.

Schlecht:

javascript
class Airplane {
  // ...
  getCruisingAltitude() {
    switch (this.type) {
      case "777":
        return this.getMaxAltitude() - this.getPassengerCount();
      case "Air Force One":
        return this.getMaxAltitude();
      case "Cessna":
        return this.getMaxAltitude() - this.getFuelExpenditure();
    }
  }
}

Gut:

javascript
class Airplane {
  // ...
}

class Boeing777 extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getPassengerCount();
  }
}

class AirForceOne extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude();
  }
}

class Cessna extends Airplane {
  // ...
  getCruisingAltitude() {
    return this.getMaxAltitude() - this.getFuelExpenditure();
  }
}

Typüberprüfung vermeiden (Teil 1)

JavaScripts Typenfreiheit verführt manchmal zu Typchecks. Konsistente APIs sind die bessere Lösung.

Schlecht:

javascript
function travelToTexas(vehicle) {
  if (vehicle instanceof Bicycle) {
    vehicle.pedal(this.currentLocation, new Location("texas"));
  } else if (vehicle instanceof Car) {
    vehicle.drive(this.currentLocation, new Location("texas"));
  }
}

Gut:

javascript
function travelToTexas(vehicle) {
  vehicle.move(this.currentLocation, new Location("texas"));
}

Typüberprüfung vermeiden (Teil 2)

Für grundlegende Typensicherheit bietet sich TypeScript an. Manuelle Typchecks beeinträchtigen oft die Lesbarkeit.

Schlecht:

javascript
function combine(val1, val2) {
  if (
    (typeof val1 === "number" && typeof val2 === "number") ||
    (typeof val1 === "string" && typeof val2 === "string")
  ) {
    return val1 + val2;
  }

  throw new Error("Must be of type String or Number");
}

Gut:

javascript
function combine(val1, val2) {
  return val1 + val2;
}

Nicht überoptimieren

Moderne Browser optimieren automatisch. Ressourcen wie Optimization Killers helfen bei echten Engpässen.

Schlecht:

javascript
// On old browsers, each iteration with uncached `list.length` would be costly
// because of `list.length` recomputation. In modern browsers, this is optimized.
for (let i = 0, len = list.length; i < len; i++) {
  // ...
}

Gut:

javascript
for (let i = 0; i < list.length; i++) {
  // ...
}

Toten Code entfernen

Ungenutzter Code ist redundant und sollte gelöscht werden. Die Versionskontrolle bewahrt benötigte Historien.

Schlecht:

javascript
function oldRequestModule(url) {
  // ...
}

function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");

Gut:

javascript
function newRequestModule(url) {
  // ...
}

const req = newRequestModule;
inventoryTracker("apples", req, "www.inventory-awesome.io");