Haben Sie eine eigene Bibliothek mit verschiedenen Hilfsprogrammen? Worauf bist du am meisten stolz? [geschlossen]


32

Ich weiß, dass viele von uns ihre eigene kleine persönliche Bibliothek mit Tools und Dienstprogrammen führen, die wir häufig verwenden.

Ich hatte meine, seit ich 16 Jahre alt war, also ist sie ziemlich groß geworden. Einige der Sachen, die ich geschrieben habe, wurden inzwischen zum Framework hinzugefügt. Ich habe meine eigene kleine Implementierung von Ausdrucksbäumen für die Verwendung mit genetischen Algorithmen lange vor LINQ geschrieben, was mir sehr gut gefallen hat und auf das ich damals stolz war - natürlich ist es jetzt ziemlich nutzlos. Aber vor kurzem habe ich es durchlaufen und ein Upgrade auf .NET 4.0 durchgeführt und ein neues Interesse geweckt.

Daher bin ich gespannt, wofür Sie Ihre Bibliothek verwenden. Vielleicht könnten wir ein paar coole Ideen für nützliche kleine Schnipsel entwickeln und sie unter uns teilen.

Meine Fragen sind also:

  • Haben Sie eine sonstige Utility-Bibliothek?
  • Auf welchen Teil bist du am meisten stolz und warum?

Geben Sie ein Beispiel für Code, wenn Sie möchten :-)


Niemand scheint zu stimmen Antworten ...
Joey Adams

@Joey Adams Richtig? Derzeit 17 Fragenstimmen und 6 Antworten insgesamt Stimmen.
Nicole

Ich sehe keine wirklich wertvollen Antworten. Was bedeutet eine Aufwertung für sie? Die Art der Frage ist so, dass die Antworten nur ein "oh. Nice" erhalten. Art von Reaktion, und dann ist es entweder alles oder gar nichts zu verbessern. (Und ich mag es nicht, jede Antwort zu verbessern, nur weil sie da ist. Wenn nichts anderes, habe ich keine Stimmen.: P)
Adam Lear

@ Anna Lear, ok, du bist entschuldigt :)
Nicole

3
Jedes anständige Dienstprogramm sollte auf Github installiert und mit der Welt geteilt werden. Es macht keinen Sinn, es versteckt zu halten, wenn es wirklich gut ist.
Job

Antworten:


27

Nein.

Ich habe einige albtraumhafte Effekte von einem Dutzend Entwicklern gesehen, die alle ihre eigenen kleinen "util.h" -Stilbibliotheken zu Projekten hinzugefügt haben und die zu einem riesigen Durcheinander inkonsistenter Funktionsnamen und -verhalten führen. Ähnlich wie PHP. Aus diesem Grund vermeide ich es.

Ich vermeide das, indem ich Programmierumgebungen verwende, die mir fast alle Tools und Bibliotheken wie C # und Python zur Verfügung stellen, die ich von vornherein benötige.


7
Ich schreibe meine Bibliothek aus organisatorischen Gründen ständig um.
Maxpm

3
Fälle, in denen das Utils-Paket albtraumhaft geworden ist, bedeuten nicht, dass alle schlecht sind. Ich kann nicht sehen, wie Sie es vermeiden können und nicht mehr Code-Duplizierung haben. Und deshalb schlechteres Testen und weniger Effizienz.
Nicole

2
@Renesis: Die utils-Pakete sind ungefähr so ​​katastrophal wie goto-Anweisungen. Sicher, an sich ist es nicht so schlimm, aber es endet scheinbar früher oder später immer mit einer Katastrophe. Wenn Sie bei der Codeduplizierung feststellen, dass Sie in praktisch allen Ihren Projekten ähnliche Aufgaben ausführen, haben dies bei Python oder C # wahrscheinlich auch andere Leute getan, und dies ist wahrscheinlich in den Standardbibliotheken der Fall.
Whatsisname

6
Nach meiner Erfahrung bevorzugen Ingenieure mit einer eigenen Bibliothek die Verwendung vor der vom System bereitgestellten, daher ist es nicht empfehlenswert, über persönliche Bibliotheken zu verfügen. Ich hatte einmal einen Mann, der absolut davon überzeugt war, dass seine 'strlen'-Funktion schneller war als die vom Compiler bereitgestellte, weil er sie geschrieben hat . Es bedurfte einer einfachen Demonstration, wie wichtig es für ihn ist, ein paar inline Montageinstruktionen zuzugeben, dass vielleicht andere Menschen es besser machen können.
JBRWilkinson

4
@ JBRWilkinson Ihr Punkt ist gut aufgenommen. Nicht jeder Programmierer ist in der Lage, gemeinsamen Code zu entwickeln.
Nicole

15

SmartFormat

Mein Lieblingsdienstprogramm ist eines, das ich geschrieben habe - ein einfacher Stringbuilder / Formatierer, mit dem es wirklich einfach ist, Daten in Strings mit korrekter Grammatik umzuwandeln.

Zum Beispiel, die meisten Programmierer bauen Text aus einer Vorlage: "There are {0} items remaining" aber dies führt zu Grammatikfehler: "There are 1 items remaining".

Also, Smart können Sie schreiben: "There {0:is|are} {0} item{0:|s} remaining".

Sie ersetzen nur String.Format(...)mit Smart.Format(...)und das wars!

Der SmartFormat- Code ist Open Source: http://github.com/scottrippey/SmartFormat/wiki


Dies erinnert mich an das von verwendete Format java.text.MessageFormat.
Barjak

@barjak Interessant! Ich habe lange nach "bedingten" Formatierungen gesucht und bis jetzt noch nichts Vergleichbares gefunden! MessageFormathat die ChoiceFormatKlasse, die eine überraschend ähnliche Syntax erlaubt! Ein Beispiel aus der Dokumentation: "There {0,choice,0#are no files|1#is one file|1<are {0,number,integer} files}.". Vielen Dank, dass Sie diesen Hinweis erwähnt haben.
Scott Rippey

@barjak Nur um meinen Standpunkt zu bestätigen, SmartFormat bietet noch viele weitere Funktionen! Die bedingte Formatierung funktioniert für jeden Datentyp, z. B. Bool, Datum, Zeitspanne und Objekt. Es unterstützt auch fortgeschrittene Operatoren wie "{Count:<0?negative|=5?five|>50&<100?large|other}". Es hat Reflektion (dh "There are {items.Length} items"kann Array-Elemente und Zeitspannen formatieren. Außerdem hat es ein Plug-in-Modell, um noch mehr Funktionen zu unterstützen.
Scott Rippey

Es scheint tatsächlich mächtig zu sein. Die Formatierung der Arrays ist interessant.
Barjak

@barjak: Ja, die Array-Formatierung ist wirklich nützlich! Schauen Sie sich dieses Beispiel an: Smart.Format("There are {0.Count} files: {0:'{}'|, |, and }.", files);würde ergeben "There are 3 files: 'a.txt', 'b.txt', and 'c.txt'.". Ich kann mir keine "Lokalisierung" ohne sie vorstellen.
Scott Rippey

7

K-Kombinator (C #, Scala)

Ich verwende den K-Kombinator in Ruby ziemlich oft, meistens in Falten, wenn der Faltvorgang eher durch einen Nebeneffekt als durch einen Rückgabewert ausgeführt wird, wie in diesem Beispiel:

some_collection.reduce(Hash.new(0)) {|acc, el| acc[el] += 1 }

Dies zählt, wie oft jedes Element in vorkommt some_collection. Leider funktioniert es nicht wirklich, da der Block bei jeder Iteration den neuen Wert des Akkumulators zurückgeben muss, aber in Ruby-Zuweisungen den zugewiesenen Wert auswertet.

Sie müssen also den neuen Wert des Akkus wie folgt zurückgeben:

some_collection.reduce(Hash.new(0)) {|acc, el| acc[el] += 1; acc }

Aber ich finde eine solche explizite Sequenzierung in diesem funktionalen Stil mit Falten hässlich. Der K-Kombinator ( Object#tapin Ruby genannt) zur Rettung:

some_collection.reduce(Hash.new(0)) {|acc, el| acc.tap { acc[el] += 1 }}

Ich habe es schon ein paar Mal in C # (meistens, weil aus irgendeinem Grund Sammlungsmutatoren wie List.Addreturn voidstatt this) und Scala verpasst , also habe ich folgendes mit mir herumgetragen :

namespace GenericExtensions
{
    public static class GenericExtensions
    {
        public static T Tap<T>(this T o, Action<T> f)
        {
            Contract.Requires(o != null);
            Contract.Requires(f != null);

            f(o);
            return o;
        }

        public static T Tap<T>(this T o, Action f)
        {
            Contract.Requires(o != null);
            Contract.Requires(f != null);

            f();
            return o;
        }
    }
}

und in Scala:

class Tap[T](o: T) {
  def tap(f: T => Unit) = { f(o); o }
  def tap(f: => Unit) = { f; o }
}

object Implicits { implicit def any2Tap[T](o: T) = new Tap(o) }

Identitätsfunktion (Ruby)

Was mir in Ruby fehlt, ist eine gut benannte Möglichkeit, auf die Identitätsfunktion zuzugreifen. Haskell bietet die Identitätsfunktion unter dem Namen idScala unter dem Namen identity. Dies ermöglicht es einem, Code wie folgt zu schreiben:

someCollection.groupBy(identity)

Das Äquivalent in Ruby ist

some_collection.group_by {|x| x }

Rollt nicht gerade die Zunge runter, oder?

Das Update ist

IDENTITY = -> x { x }

some_collection.group_by(&IDENTITY)

ForEach (.NET)

Eine andere schmerzlich fehlende Methode in C #:

namespace IEnumerableExtensions
{
    public static class IEnumerableExtensions
    {
        public static void ForEach<T>(this IEnumerable<T> xs, Action<T> f)
        {
            Contract.Requires(xs != null);
            Contract.Requires(f != null);

           foreach (var x in xs) f(x);
        }
    }
}

3
Ich denke, Ihr letztes Beispiel war eine kalkulierte Entwurfsentscheidung. Das Konzept von Actionimpliziert Nebenwirkungen, die den Gestaltungsprinzipien von LINQ zuwiderlaufen.
ChaosPandion

1
@ChaosPandion: Was hat das mit LINQ zu tun?
Jörg W Mittag

@ Jörg W Mittag - Die IEnumerableErweiterungen wurden für LINQ hinzugefügt.
ChaosPandion

2
@ChaosPandion: Ich verstehe immer noch nicht. ForEachist kein LINQ-Operator. Warum sollten Einschränkungen gelten, die nur für LINQ-Operatoren gelten ForEach, bei denen es sich nicht um einen LINQ-Operator handelt? Und warum sind Nebenwirkungen verboten, IEnumerable.ForEachaber erlaubt List.ForEach? Auch, warum sind Nebenwirkungen verboten, IEnumerable.ForEachaber erlaubt foreach?
Jörg W Mittag

@ Jörg W Mittag - Was ich sage ist die Tatsache, dass es bei den Erweiterungen fehlte, war eine gestalterische Entscheidung. Die Tatsache, dass a List<T>hat, ForEachist vernünftig, wenn man bedenkt, dass es ein wandelbarer Typ ist.
ChaosPandion

6

Ich habe einen Java Type Converter. Es hat eine öffentliche Unterschrift

public static <T> T convert(Object sourceValue, Class<T> destinationType)

und es tut sein Bestes, um den Quellwert in den Zieltyp zu konvertieren. Im Grunde genommen können Sie damit in einer statisch getippten Sprache dynamisch tippen :-)

Es ist eigentlich nützlich mit numerischen Boxed-Typen. Wie irritierend ist es, dass man nicht dorthin bringen kann Integer, wo man Longes erwartet? Kein Problem, konvertieren Sie es einfach. Oder was ist, wenn deine Funktion eine erwartet double, du aber eine nulldort ablegen musst ? Kaboom, eine NPE. Aber setzen Sie es durch convert, und Sie bekommen ein NaN.


Interessante Lösung. Ich habe immer gedacht, dass Long Integer erweitern sollte. Aber selbst dann hätten Sie immer noch das Autoboxing-Problem (soweit ich weiß, würde Autoboxing mit der Vererbung nicht funktionieren). Auch +1 für die NaNUnterstützung.
Nicole

NaNist ausgezeichnet. Schade, dass es für ganze Zahlen so etwas nicht gibt. Ich habe Integer.MIN_VALUEals Konvention verwendet. Es ist normalerweise "seltsam genug", um bemerkt zu werden, im Gegensatz zum Standardwert 0. Ich weiß nicht, warum das automatische (un) Boxen nicht (Double) nullals behandelt wird NaN. Es ist die offensichtlich richtige Lösung, IMHO.
Joonas Pulakka

6

Von dem Code, den ich geschrieben habe, befinden sich die meisten guten Dinge in CCAN , während der Rest in bestehenden Open-Source-Projekten tendenziell bessere Versionen findet. Heutzutage schreibe ich immer weniger Universal-Misc-Code, um anwendungsspezifische Varianten dieses Codes zu schreiben, oder um Universal-Module zu schreiben, die ich selbst freigeben kann.

C

Hier ist eine Funktion und typedef, die ich mehr als einmal verwendet habe. Für Anwendungen, die ein Timing benötigen, ist es in Bezug auf die Einfachheit schwer, Millisekunden zu übertreffen:

#include <stdint.h>
#include <sys/time.h>

typedef int64_t msec_t;

static msec_t time_ms(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (msec_t)tv.tv_sec * 1000 + tv.tv_usec / 1000;
}

Und noch mehr verschiedene C-Funktionen, die ich immer und immer wieder (und immer wieder) benutze:

/* Remove a trailing newline, if present. */
void chomp(char *buffer)
{
    if (!*buffer)
        return;

    while (*buffer)
        buffer++;

    if (buffer[-1] == '\n')
        buffer[-1] = 0;
}

/*
 * Skip whitespace, update the pointer, and return it.
 * Example:
 *
 * switch (*skipSpace(&s)) {
 *     case '\0':
 *         ...
 *     case '(':
 *         ...
 */
const char *skipSpace(const char **sptr)
{
    const char *s = *sptr;
    while (isspace(*s))
        s++;
    *sptr = s;
    return s;
}

/* Scramble an array of items uniformly. */
void scramble(void *base, size_t nmemb, size_t size)
{
    char *i = base;
    char *o;
    size_t sd;
    for (;nmemb>1;nmemb--) {
        o = i + size*(rand()%nmemb);
        for (sd=size;sd--;) {
            char tmp = *o;
            *o++ = *i;
            *i++ = tmp;
        }
    }
}

Haskell

Haskells nub :: (Eq a) => [a] -> [a]Funktion ist O (n²), weil anhand der Typensignatur nur geprüft werden darf, ob zwei Elemente gleich sind. Eine einfache O (n log n) -Alternative ist map head . group . sort, dass jedoch die gesamte Eingabeliste vor der Ausgabe erzwungen werden muss, während nubdie Ausgabe sofort beginnen kann. Das Folgende ist eine Alternative zu O (n log n) nub, die bereits gesehene Elemente in einem sammelt Data.Set:

module Nub (nub') where

import Prelude
import Data.Set (empty, member, insert)

nub' :: Ord a => [a] -> [a]
nub' xs = loop xs empty where
    loop [] _ = []
    loop (x:xs) set =
        if x `member` set
            then loop xs set
            else x : loop xs (insert x set)

In Haskell, verwende ich Alternativen zu sequence, mapM, forM, replicateM, und filterM. Diese Aktionen generieren jeweils eine Liste, aber die Liste kann erst verwendet werden, wenn die Aktion vollständig abgeschlossen ist (wenn Sie eine strenge Monade wie IO verwenden). Die Alternativen bilden die Liste in umgekehrter Reihenfolge, anstatt einen Turm aus Thunks zu bilden, was ich durch Benchmarking zumindest mit GHC als schneller empfand.

sequence' :: Monad m => [m a] -> m [a]
sequence' ms = loop ms [] >>= return . reverse where
    loop []     xs = return xs
    loop (m:ms) xs = do
        x <- m
        loop ms (x:xs)

mapM' :: Monad m => (a -> m b) -> [a] -> m [b]
mapM' f xs = sequence' $ map f xs

forM' :: Monad m => [a] -> (a -> m b) -> m [b]
forM' = flip mapM'

replicateM' :: Monad m => Int -> m a -> m [a]
replicateM' n x = sequence' (replicate n x)

filterM' :: Monad m => (a -> m Bool) -> [a] -> m [a]
filterM' pred xs = loop xs [] >>= return . reverse where
    loop []     xs' = return xs'
    loop (x:xs) xs' = do
        keep <- pred x
        loop xs (if keep then (x:xs') else xs')

Hinweis: sequence_, mapM_, forM_, und replicateM_Funktionen sind immer noch eine bessere Wahl , wenn Sie in der Ergebnisliste nicht interessiert sind.


+1 für CCAN, obwohl ich ein bisschen voreingenommen sein könnte :)
Tim Post

4

Ich ende damit, split / join ala Perl in Sprachen zu implementieren, die es nicht haben.

Ich habe atoi und itoa auch öfter in C reimplementiert, als ich denken möchte (Embedded Systems Junk).


4

Nein.

Ich programmiere den größten Teil in Java, und es wird empfohlen, "utils" aus Apache Commons-Bibliotheken und ähnlichen Projekten wiederzuverwenden.

Wenn Sie objektiv sind, gibt es nur wenige Fälle, in denen Ihre eigene "utils" -Sammlung eine signifikante Verbesserung gegenüber dem darstellt, was andere bereits getan haben. Und wenn es sich nicht um eine Verbesserung handelt, ist Ihre Utils-Bibliothek wahrscheinlich eine Verschwendung von Entwicklungszeit und ein Ärgernis / eine Belastung für zukünftige Betreuer.


3

Ich hatte einige Datumsmanipulationen , die ich mit Java durchgeführt habe, und fing dann an, JodaTime zu verwenden, da ich gute Dinge darüber gehört hatte und es in Java 7 enthalten sein sollte (nicht sicher, ob dies immer noch der Fall ist, aber auch wenn es nicht immer noch der Fall ist) lohnt sich imho).

Es verwandelte eine über 50-Zeilen-Klasse in eine Zeile mit ungefähr drei verketteten Methodenaufrufen.

Für die Neugierigen ging es darum, das Datum für jeden Tag von zu bekommen vergangenen n Wochen zu ermitteln: zB die Verkaufszahlen für einen Montag vor 10 Wochen usw. usw.).

Und hier ist ein Teil davon

public static DateTime getDayPreviousWeek(DateTime dt, DayOfWeek dayOfWeek, int n_weeks) {
       return dt.minusWeeks(n_weeks).dayOfWeek().setCopy(dayOfWeek.getDayAsString());
}

Java hat Erweiterungsmethoden?
Kugel

Nein, aber ich denke, es könnte sein, dass sie in Version 7
NimChimpsky

2

Ich habe immer eine utilsArt Paket, auch in Java, aber meine PHP-Utils-Sammlung wird am häufigsten wiederverwendet. Es gibt so viele gute Bibliotheken in Java, dass ich entweder bereits eine Bibliothek im Projekt habe oder nur ein paar fehlende Werkzeuge selbst entwerfen muss. PHP-Bibliotheken tun in der Regel zu viel, als dass ich sie in meine Projekte einbeziehen möchte.

Ich mag diese Funktion für PHP, verfeinert mit Hilfe von StackOverflow ...

function getValueFromDotKey(&$context, $name) {
    $pieces = explode('.', $name);
    foreach ($pieces as $piece) {
        if (!is_array($context) || !array_key_exists($piece, $context)) {
            // error occurred
            return null;
        }
        $context = &$context[$piece];
    }
    return $context;
}

Es ähnelt Apaches BeanUtils für Java, und ich verwende es für einen ähnlichen Zweck, indem ich Formularelementen in einer Vorlagensprache einen einzelnen Schlüssel gebe, mit dem ein verschachtelter Wert in einem Quell-Array abgerufen / festgelegt werden kann:

$source = array('a' => array('b' => 5));

$val = getValueFromDotKey($source, 'a.b');

Natürlich ist PHP, wollte ich die Methode so leicht wie möglich zu halten , damit es nicht ist ganz wie featureful wie BeanUtils;)


2

In der Scala-Standardbibliothek fehlen einige der am häufigsten verwendeten Funktionen höherer Ordnung.

Zwei solche Funktionen, die ich am häufigsten benötige:

// #1: unfold
def unfold[T, R](init: T)(f: T => Option[(R, T)]): List[R] = f(init) match {
  case None => Nil
  case Some(r, v) => r :: unfold(v)(f)
}

// #2: zipWith
def zipWith[A, B, C](xs: List[A], ys: List[B])(f: (A, B) => C): List[C] = {
  (xs, ys).zipped.map(f)
}

1

Zurzeit nicht. Ich hatte eine, als ich C machte, aber jetzt, wo ich Java mache, macht es keinen Sinn mehr, wenn man alle verfügbaren Standard-Bibliotheken und alle Goodies aus dem Apache-Projekt berücksichtigt.

Eines der nützlichen Dinge in meiner C-Bibliothek war eine schnelle und schmutzige Implementierung einer Finite-State-Maschine, die die Definition einer Finite-State-Maschine mit nur zwei Zeichenfolgen und einer Reihe von Zeichenfolgen ermöglichte. Es könnte verwendet werden, um Zeichenfolgen mit Regeln abzugleichen (z. B. "muss 4 bis 6 Zeichen lang sein, zuerst ein Buchstabe, Restziffern"), aber die Verfügbarkeit von regulären Ausdrücken machte das Ding völlig sinnlos.


1

Ich kann jetzt keine Desktop-Benutzeroberflächen ohne dynamische Dialogfelder schreiben , die auf der differenziellen Ausführung basieren . Es ist ein Hack, über den ich um 1985 gestolpert bin, und ich habe ihn mehrmals in verschiedenen Sprachen implementiert, als ich mich erinnern kann.


1

Ich habe festgestellt, dass ich in Django eine Menge des gleichen Codes geschrieben habe. Mach dieses gemeinsame Ding, dann dieses gemeinsame Ding und schließlich dieses gemeinsame Ding. Grundsätzlich können Sie ein oder mehrere Elemente aus der Datenbank abrufen oder die Ergebnisse eines Formulars speichern.

Wenn jedes dieser Dinge nur einmal in einer Ansicht vorkommt, kann ich die generischen Django-Ansichten verwenden. Leider sind diese nicht wirklich komponierbar, und ich musste mehrere Dinge nacheinander erledigen.

Also habe ich eine noch allgemeinere Ansichtenbibliothek geschrieben, bei der zuerst eine Liste von Aktionen aus relevanten Abfragesätzen (oder was auch immer) erstellt und dann die Liste in eine Ansicht eingeschlossen wurde.

Ich muss noch einige Ansichten von Hand schreiben, aber diese sind normalerweise so komplex, dass nicht viel wiederverwendbar ist. Alle Boilerplates landen nur an anderer Stelle, entweder als generische Ansicht oder als Ansichtsdekorateur (häufig als dekorierte generische Ansicht). Dies macht in der Regel etwa 10% der von mir geschriebenen Handler aus, da einige generische Handler alles andere können.


1

Ja, aber nur für domänenspezifische Redewendungen (wie spielobjektspezifische Container).

Da es sich eher um einfache Hilfsprogramme als um komplexe Dinge handelt, bin ich auf nichts stolz. Ich bin im Moment sowieso der einzige Benutzer, also gibt es nichts, auf das man stolz sein kann.


1

Indirekte C ++ - Sortierung, basierend auf der AWL sortund einer Funktionsvorlage.

Die Notwendigkeit einer indirekten Sortierung (bei der die gewünschte Ausgabe die Permutationsindizes waren , die sich aus der Sortierung der Daten ergeben würden, nicht jedoch die sortierten Daten selbst) trat in einer Reihe von Projekten häufig auf. Ich habe mich immer gefragt, warum STL keine Implementierung dafür bereitstellt.

Ein anderer war ein zyklischer C ++ - Vektor, bei dem positive und negative Indizes mit der Vektorgröße modulo sind (sodass alle ganzzahligen Werte gültige Indizes für den Vektor sind).


-4

Ich habe ein kleines Utils-Paket geschrieben, als ich in meinem Comp Java-Entwicklung machte. Sci-Klasse in der High School. Ich bin sehr stolz auf meinen Zufallsgenerator.

/**
* Returns a random integer.
*
* @returns    int    Random integer
*/
public static int getRandom()
{
    return 4; // Chosen by a fair dice roll.
              // Guaranteed to be random.
}

Stützt sich auf meine Inspiration.


12
Komm schon, xkcd ....
Darknight

2
Komm schon, es ist egal.
Josh K

1
Mit Ihren aktuellen Stimmen bei -2
denke

8
Plagiat ist die höchste Form der Schmeichelei, außer wenn es offensichtlich ist.
Maxpm

5
Nun, der Downvote-Button sagt: "Diese Antwort ist nicht nützlich". Ich denke, es ist ein zusätzlicher Knopf erforderlich: "... aber sicher ist lustig"
skajfes
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.