¿Existen pautas sobre cuántos parámetros debe aceptar una función?


114

He notado que algunas funciones con las que trabajo tienen 6 o más parámetros, mientras que en la mayoría de las bibliotecas que uso es raro encontrar una función que requiera más de 3.

A menudo, muchos de estos parámetros adicionales son opciones binarias para alterar el comportamiento de la función. Creo que algunas de estas innumerables funciones parametrizadas probablemente deberían ser refactorizadas. ¿Hay una guía para qué número es demasiado?


44
@Ominus: La idea es que quieras mantener tus clases enfocadas. Las clases enfocadas generalmente no tienen tantas dependencias / atributos, por lo tanto, tendría menos parámetros para el constructor. Algunas palabras de moda que la gente lanza en este punto es la alta cohesión y el principio de responsabilidad única . Si cree que no se viola y aún necesita muchos parámetros, considere usar el patrón Builder.
c_maker

2
Definitivamente no sigas el ejemplo de MPI_Sendrecv () , que toma 12 parámetros.
chrisaycock

66
El proyecto en el que estoy trabajando actualmente utiliza un cierto marco, en el que los métodos con más de 10 parámetros son comunes. Llamo a un método particular con 27 parámetros en un par de lugares. Cada vez que lo veo me muero un poco por dentro.
Perp

3
Nunca agregue modificadores booleanos para alterar el comportamiento de la función. Divida la función en su lugar. Divide el comportamiento común en nuevas funciones.
Kevin Cline

2
@Ominus ¿Qué? ¿Solo 10 parámetros? Eso no es nada, se necesita mucho más . : D
maaartinus

Respuestas:


106

Nunca he visto una directriz, pero en mi experiencia una función que toma más de tres o cuatro parámetros indica uno de dos problemas:

  1. La función está haciendo demasiado. Debe dividirse en varias funciones más pequeñas, cada una de las cuales tiene un conjunto de parámetros más pequeño.
  2. Hay otro objeto escondido allí. Es posible que deba crear otro objeto o estructura de datos que incluya estos parámetros. Consulte este artículo sobre el patrón de objeto de parámetro para obtener más información.

Es difícil saber lo que estás viendo sin más información. Lo más probable es que la refactorización que necesita hacer es dividir la función en funciones más pequeñas que se invocan desde el elemento primario dependiendo de los indicadores que actualmente se pasan a la función.

Al hacer esto, hay algunas buenas ganancias:

  • Hace que su código sea más fácil de leer. Personalmente, me resulta mucho más fácil leer una "lista de reglas" compuesta de una ifestructura que llama a muchos métodos con nombres descriptivos que una estructura que lo hace todo en un solo método.
  • Es más comprobable por unidad. Ha dividido su problema en varias tareas más pequeñas que son individualmente muy simples. La colección de pruebas unitarias se compondría de un conjunto de pruebas de comportamiento que verifica las rutas a través del método maestro y una colección de pruebas más pequeñas para cada procedimiento individual.

55
¿La abstracción de parámetros se ha convertido en un patrón de diseño? ¿Qué sucede si tienes 3 clases de parámetros? ¿Agrega 9 sobrecargas de métodos más para manejar las diferentes combinaciones posibles de parámetros? Eso suena como un problema desagradable de declaración de parámetro O (n ^ 2). Oh, espera, solo puedes heredar 1 clase en Java / C #, por lo que requerirás más biolerplate (tal vez más subclases) para que funcione en la práctica. Lo siento, no estoy convencido. Ignorar los enfoques más expresivos que puede ofrecer un lenguaje a favor de la complejidad simplemente se siente mal.
Evan Plaice

A menos que use el patrón Objeto de patrón para empaquetar variables en una instancia de objeto y pasarlo como parámetro. Eso funciona para el empaquetado, pero puede ser tentador crear clases de variables diferentes solo por la conveniencia de simplificar la definición del método.
Evan Plaice

@EvanPlaice No estoy diciendo que tenga que usar ese patrón cada vez que tenga más de un parámetro; tiene toda la razón de que eso se vuelve aún más desagradable que la primera lista. Puede haber casos en los que realmente necesite una gran cantidad de parámetros y simplemente no funcione para envolverlos en un objeto. Todavía tengo que encontrar un caso en el desarrollo empresarial que no caiga en uno de los dos segmentos que mencioné en mi respuesta; eso no significa que uno no exista.
Michael K

@MichaelK Si nunca los ha usado, intente buscar en Google 'inicializadores de objetos'. Es un enfoque bastante novedoso que reduce significativamente la definición repetitiva. En teoría, podría eliminar los constructores de clases, los parámetros y las sobrecargas de una sola vez. Sin embargo, en la práctica, generalmente es bueno mantener un constructor común y confiar en la sintaxis de 'inicializador de objeto' para el resto de las propiedades oscuras / nicho. En mi humilde opinión, es lo más cerca que está de la expresividad de los idiomas escritos dinámicamente en un idioma estáticamente escrito.
Evan Plaice

@Evain Plaice: ¿Desde cuándo los lenguajes escritos dinámicamente son expresivos?
ThomasX

41

Según el "Código limpio: un manual de artesanía ágil de software", cero es el ideal, uno o dos son aceptables, tres en casos especiales y cuatro o más, ¡nunca!

Las palabras del autor:

El número ideal de argumentos para una función es cero (niládico). Luego viene uno (monádico), seguido de cerca por dos (diádico). Se deben evitar tres argumentos (triádicos) siempre que sea posible. Más de tres (poliádico) requieren una justificación muy especial, y de todos modos no deberían usarse.

En este libro hay un capítulo que habla solo de las funciones en las que se discuten los parámetros, por lo que creo que este libro puede ser una buena guía de cuántos parámetros necesita.

En mi opinión personal, un parámetro es mejor que nadie porque creo que está más claro lo que está sucediendo.

Como ejemplo, en mi opinión, la segunda opción es mejor porque es más claro lo que el método está procesando:

LangDetector detector = new LangDetector(someText);
//lots of lines
String language = detector.detectLanguage();

vs.

LangDetector detector = new LangDetector();
//lots of lines
String language = detector.detectLanguage(someText);

Acerca de muchos parámetros, esto puede ser una señal de que algunas variables se pueden agrupar en un solo objeto o, en este caso, muchos valores booleanos pueden representar que la función / método está haciendo más de una cosa, y en este caso, Es mejor refactorizar cada uno de estos comportamientos en una función diferente.


8
"¡Tres en casos especiales y cuatro o más, nunca!" BS. ¿Qué tal Matrix.Create (x1, x2, x3, x4, x5, x6, x7, x8, x9); ?
Lukasz Madon

71
¿Cero es ideal? ¿Cómo diablos las funciones obtienen información? ¿Global / instancia / estática / cualesquiera variables? YUCK
Peter C

99
Ese es un mal ejemplo. La respuesta es, obviamente: String language = detectLanguage(someText);. En cualquiera de sus casos, pasó exactamente el mismo número de argumentos, simplemente sucede que ha dividido la ejecución de la función en dos debido a un lenguaje deficiente.
Matthieu M.

8
@lukas, en los idiomas que admiten construcciones tan sofisticadas como matrices o listas (¡jadeo!), ¿qué tal Matrix.Create(input);si input, por ejemplo, hay un .NET IEnumerable<SomeAppropriateType>? De esa manera tampoco necesita una sobrecarga separada cuando desea crear una matriz que contenga 10 elementos en lugar de 9.
un CVn

99
Cero argumentos como "ideal" es una trampa y una razón por la que creo que Clean Code está sobrevalorado.
user949300

24

Si las clases de dominio en la aplicación están diseñadas correctamente, la cantidad de parámetros que pasamos a una función se reducirá automáticamente, porque las clases saben cómo hacer su trabajo y tienen suficientes datos para hacer su trabajo.

Por ejemplo, supongamos que tiene una clase de gerente que le pide a una clase de tercer grado que complete las tareas.

Si modelas correctamente,

3rdGradeClass.finishHomework(int lessonId) {
    result = students.assignHomework(lessonId, dueDate);
    teacher.verifyHomeWork(result);
}

Esto es simple.

Si no tiene el modelo correcto, el método será así

Manager.finishHomework(grade, students, lessonId, teacher, ...) {
    // This is not good.
}

El modelo correcto siempre reduce los parámetros de función entre las llamadas a métodos, ya que las funciones correctas se delegan a sus propias clases (responsabilidad única) y tienen suficientes datos para realizar su trabajo.

Cada vez que veo que aumenta el número de parámetros, verifico mi modelo para ver si diseñé mi modelo de aplicación correctamente.

Sin embargo, hay algunas excepciones: cuando necesito crear un objeto de transferencia u objetos de configuración, usaré un patrón de construcción para producir objetos construidos pequeños primero antes de construir un objeto de configuración grande.


16

Un aspecto que las otras respuestas no abordan es el rendimiento.

Si está programando en un lenguaje de nivel suficientemente bajo (C, C ++, ensamblaje), una gran cantidad de parámetros puede ser bastante perjudicial para el rendimiento en algunas arquitecturas, especialmente si la función se llama una gran cantidad de veces.

Cuando una llamada de función se realiza en ARM por ejemplo, los primeros cuatro argumentos se colocan en registros r0a r3y argumentos restantes tienen que ser empujado en la pila. Mantener el número de argumentos por debajo de cinco puede marcar una gran diferencia para las funciones críticas.

Para las funciones que se llaman muy a menudo, incluso el hecho de que el programa tiene que establecer los argumentos antes de cada llamada puede afectar al rendimiento ( r0a r3pueden ser sobrescritos por la función llamada y tendrá que ser reemplazado antes de la siguiente llamada) por lo que en ese sentido cero argumentos son los mejores.

Actualizar:

KjMag trae a colación el interesante tema de la alineación. La alineación mitigará esto de alguna manera, ya que permitirá que el compilador realice las mismas optimizaciones que usted podría hacer si escribiera en ensamblado puro. En otras palabras, el compilador puede ver qué parámetros y variables utiliza la función llamada y puede optimizar el uso del registro para minimizar la lectura / escritura de la pila.

Sin embargo, hay algunos problemas con la alineación.

  1. La alineación hace que el binario compilado crezca ya que el mismo código se duplica en forma binaria si se llama desde múltiples lugares. Esto es perjudicial cuando se trata del uso de I-cache.
  2. Los compiladores generalmente solo permiten alinearse hasta cierto nivel (¿3 pasos IIRC?). Imagine llamar a una función en línea desde una función en línea desde una función en línea. El crecimiento binario explotaría si inlinefuera tratado como obligatorio en todos los casos.
  3. Hay muchos compiladores que ignorarán por completo inlineo realmente le darán errores cuando lo encuentren.

Si pasar una gran cantidad de parámetros es bueno o malo desde una perspectiva de rendimiento depende de las alternativas. Si un método necesita una docena de datos, y uno lo llamará cientos de veces con los mismos valores para once de ellos, hacer que el método tome una matriz podría ser más rápido que hacer que tome una docena de parámetros. Por otro lado, si cada llamada necesitará un conjunto único de doce valores, crear y completar la matriz para cada llamada podría ser más lento que simplemente pasar los valores directamente.
supercat

¿La alineación no resuelve este problema?
KjMag

@KjMag: Sí, hasta cierto punto. Pero hay muchas trampas dependiendo del compilador. Por lo general, las funciones solo se alinearán hasta cierto nivel (si llama a una función en línea que llama a una función en línea que llama a una función en línea ...). Si la función es grande y se llama desde muchos lugares, la alineación en todas partes hace que el binario sea más grande, lo que puede significar más errores en el I-cache. Por lo tanto, la alineación puede ayudar, pero no es una bala de plata. (Sin mencionar que hay bastantes compiladores incrustados antiguos que no son compatibles inline)
Leo

7

Cuando la lista de parámetros crezca a más de cinco, considere definir una estructura u objeto de "contexto".

Esto es básicamente una estructura que contiene todos los parámetros opcionales con algunos valores predeterminados razonables establecidos.

En el mundo procesal C, una estructura simple sería suficiente. En Java, C ++ un objeto simple será suficiente. No te metas con getters o setters porque el único propósito del objeto es mantener valores "públicos" configurables.


Estoy de acuerdo, un objeto de contexto puede resultar muy útil cuando las estructuras de parámetros de funciones comienzan a ser bastante complejas. Recientemente escribí en un blog sobre el uso de objetos de contexto con un patrón similar al de un visitante
Lukas Eder,

5

No, no hay una directriz estándar.

Pero hay algunas técnicas que pueden hacer que una función con muchos parámetros sea más soportable.

Puede usar un parámetro list-if-args (args *) o un parámetro dictionary-of-args (kwargs **)

Por ejemplo, en python:

// Example definition
def example_function(normalParam, args*, kwargs**):
  for i in args:
    print 'args' + i + ': ' + args[i] 
  for key in kwargs:
    print 'keyword: %s: %s' % (key, kwargs[key])
  somevar = kwargs.get('somevar','found')
  missingvar = kwargs.get('somevar','missing')
  print somevar
  print missingvar

// Example usage

    example_function('normal parameter', 'args1', args2, 
                      somevar='value', missingvar='novalue')

Salidas:

args1
args2
somevar:value
someothervar:novalue
value
missing

O podría usar la sintaxis de definición literal de objeto

Por ejemplo, aquí hay una llamada jQuery de JavaScript para lanzar una solicitud AJAX GET:

$.ajax({
  type: 'GET',
  url: 'http://someurl.com/feed',
  data: data,
  success: success(),
  error: error(),
  complete: complete(),
  dataType: 'jsonp'
});

Si echa un vistazo a la clase ajax de jQuery, hay muchas (aproximadamente 30) propiedades más que se pueden configurar; principalmente porque las comunicaciones ajax son muy complejas. Afortunadamente, la sintaxis literal del objeto facilita la vida.


C # intellisense proporciona documentación activa de parámetros, por lo que no es raro ver arreglos muy complejos de métodos sobrecargados.

Los lenguajes de tipo dinámico como python / javascript no tienen esa capacidad, por lo que es mucho más común ver argumentos de palabras clave y definiciones literales de objetos.

Prefiero definiciones literales de objeto ( incluso en C # ) para administrar métodos complejos porque puede ver explícitamente qué propiedades se establecen cuando se instancia un objeto. Tendrá que hacer un poco más de trabajo para manejar los argumentos predeterminados, pero a la larga su código será mucho más legible. Con las definiciones literales de objetos, puede romper su dependencia de la documentación para comprender lo que hace su código a primera vista.

En mi humilde opinión, los métodos sobrecargados están muy sobrevalorados.

Nota: si recuerdo correctamente, el control de acceso de solo lectura debería funcionar para los constructores literales de objetos en C #. Básicamente funcionan igual que establecer propiedades en el constructor.


Si nunca ha escrito ningún código no trivial en un lenguaje basado en javaScript de tipo dinámico (python) y / o funcional / prototipo, le recomiendo que lo pruebe. Puede ser una experiencia esclarecedora.

Puede ser aterrador primero romper su dependencia de los parámetros para el enfoque final de la inicialización de funciones / métodos, pero aprenderá a hacer mucho más con su código sin tener que agregar una complejidad innecesaria.

Actualizar:

Probablemente debería haber proporcionado ejemplos para demostrar el uso en un lenguaje de tipo estático, pero actualmente no estoy pensando en un contexto de tipo estático. Básicamente, he estado haciendo demasiado trabajo en un contexto de tipo dinámico para cambiar repentinamente.

Lo que sé es que la sintaxis de definición literal de objetos es completamente posible en lenguajes estáticamente tipados (al menos en C # y Java) porque los he usado antes. En lenguajes tipados estáticamente se llaman 'Inicializadores de objetos'. Aquí hay algunos enlaces para mostrar su uso en Java y C # .


3
No estoy seguro de que me guste este método, principalmente porque pierde el valor de autodocumentación de los parámetros individuales. Para listas de elementos similares, esto tiene mucho sentido (por ejemplo, un método que toma una lista de cadenas y las concatena), pero para un conjunto de parámetros arbitrarios, esto es peor que la llamada larga al método.
Michael K

@MichaelK Eche otro vistazo a los inicializadores de objetos. Le permiten definir explícitamente propiedades en lugar de cómo están implícitamente definidas en los parámetros tradicionales de método / función. Lea esto, msdn.microsoft.com/en-us/library/bb397680.aspx , para ver a qué me refiero.
Evan Plaice

3
Crear un nuevo tipo solo para manejar la lista de parámetros suena exactamente como la definición de complejidad innecesaria ... Claro, los lenguajes dinámicos le permiten evitar eso, pero luego obtiene una sola bola de parámetro goo. De todos modos, esto no responde a la pregunta formulada.
Telastyn

@Telastyn ¿De qué estás hablando? No se creó ningún tipo nuevo, declara propiedades directamente utilizando la sintaxis literal del objeto. Es como definir un objeto anónimo, pero el método lo interpreta como una agrupación de parámetros clave = valor. Lo que estás viendo es una instanciación de método (no un objeto de encapsulación de parámetros). Si su carne de res está con el empaquetado de parámetros, eche un vistazo al patrón de objeto de parámetro mencionado en una de las otras preguntas porque eso es exactamente lo que es.
Evan Plaice

@EvanPlaice: excepto que los lenguajes de programación estáticos en general requieren un tipo declarado (a menudo nuevo) para permitir el patrón de objeto de parámetro.
Telastyn

3

Personalmente, más de 2 es donde se dispara mi código de alarma de olor. Cuando considera que las funciones son operaciones (es decir, una traducción de entrada a salida), es raro que se usen más de 2 parámetros en una operación. Los procedimientos (que son una serie de pasos para lograr un objetivo) requerirán más aportes y, a veces, son el mejor enfoque, pero en la mayoría de los idiomas en estos días no deberían ser la norma.

Pero, de nuevo, esa es la guía más que una regla. A menudo tengo funciones que toman más de dos parámetros debido a circunstancias inusuales o facilidad de uso.


2

Al igual que dice Evan Plaice, soy un gran admirador de simplemente pasar matrices asociativas (o la estructura de datos comparable de su idioma) a funciones siempre que sea posible.

Por lo tanto, en lugar de (por ejemplo) esto:

<?php

createBlogPost('the title', 'the summary', 'the author', 'the date of publication, 'the keywords', 'the category', 'etc');

?>

Ir por:

<?php

// create a hash of post data
$post_data = array(
  'title'    => 'the title',
  'summary'  => 'the summary',
  'author'   => 'the author',
  'pubdate'  => 'the publication date',
  'keywords' => 'the keywords',
  'category' => 'the category',
  'etc'      => 'etc',
);

// and pass it to the appropriate function
createBlogPost($post_data);

?>

Wordpress hace muchas cosas de esta manera, y creo que funciona bien. (Aunque mi código de ejemplo anterior es imaginario, y no es en sí mismo un ejemplo de Wordpress).

Esta técnica le permite pasar una gran cantidad de datos a sus funciones fácilmente, pero le libera de tener que recordar el orden en que cada uno debe pasar.

También apreciará esta técnica cuando llegue el momento de refactorizar; en lugar de tener que cambiar potencialmente el orden de los argumentos de una función (como cuando se da cuenta de que necesita pasar Yet Another Argument), no necesita cambiar el parámetro de sus funciones lista en absoluto.

Esto no solo le evita tener que volver a escribir la definición de su función, sino que le ahorra tener que cambiar el orden de los argumentos cada vez que se invoca la función. Esa es una gran victoria.


Ver esto publicado resalta otro beneficio del enfoque de pasar un hash: tenga en cuenta que mi primer ejemplo de código es tan largo que genera una barra de desplazamiento, mientras que el segundo encaja perfectamente en la página. Lo mismo probablemente sea cierto en su editor de código.
Chris Allen Lane,

0

Una respuesta anterior mencionó a un autor confiable que declaró que cuantos menos parámetros tengan sus funciones, mejor será su desempeño. La respuesta no explica por qué, pero los libros lo explican, y aquí hay dos de las razones más convincentes de por qué necesita adoptar esta filosofía y con la que personalmente estoy de acuerdo:

  • Los parámetros pertenecen a un nivel de abstracción que es diferente del de la función. Esto significa que el lector de su código tendrá que pensar sobre la naturaleza y el propósito de los parámetros de sus funciones: este pensamiento es de "nivel inferior" que el del nombre y el propósito de sus funciones correspondientes.

  • La segunda razón para tener la menor cantidad posible de parámetros para una función es la prueba: por ejemplo, si tiene una función con 10 parámetros, piense cuántas combinaciones de parámetros tiene para cubrir todos los casos de prueba para, por ejemplo, una unidad prueba. Menos parámetros = menos pruebas.


0

Para proporcionar algo más de contexto en torno al consejo para que el número ideal de argumentos de función sea cero en el "Código limpio: un manual de artesanía ágil de software" de Robert Martin, el autor dice lo siguiente como uno de sus puntos:

Los argumentos son difíciles. Toman mucho poder conceptual. Es por eso que me libré de casi todos ellos del ejemplo. Considere, por ejemplo, el StringBufferen el ejemplo. Podríamos haberlo pasado como un argumento en lugar de convertirlo en una variable de instancia, pero nuestros lectores habrían tenido que interpretarlo cada vez que lo vieran. Cuando estás leyendo la historia contada por el módulo, includeSetupPage() es más fácil de entender que includeSetupPageInto(newPageContent). El argumento está en un nivel diferente de abstracción que el nombre de la función y te obliga a conocer un detalle (en otras palabras StringBuffer) que no es particularmente importante en ese punto.

Para su includeSetupPage()ejemplo anterior, aquí hay un pequeño fragmento de su "código limpio" refactorizado al final del capítulo:

// *** NOTE: Commments are mine, not the author's ***
//
// Java example
public class SetupTeardownIncluder {
    private StringBuffer newPageContent;

    // [...] (skipped over 4 other instance variables and many very small functions)

    // this is the zero-argument function in the example,
    // which calls a method that eventually uses the StringBuffer instance variable
    private void includeSetupPage() throws Exception {
        include("SetUp", "-setup");
    }

    private void include(String pageName, String arg) throws Exception {
        WikiPage inheritedPage = findInheritedPage(pageName);
        if (inheritedPage != null) {
            String pagePathName = getPathNameForPage(inheritedPage);
            buildIncludeDirective(pagePathName, arg);
        }
    }

    private void buildIncludeDirective(String pagePathName, String arg) {
        newPageContent
            .append("\n!include ")
            .append(arg)
            .append(" .")
            .append(pagePathName)
            .append("\n");
    }
}

La "escuela de pensamiento" del autor argumenta a favor de clases pequeñas, bajo (idealmente 0) número de argumentos de función y funciones muy pequeñas. Si bien tampoco estoy totalmente de acuerdo con él, me pareció estimulante y creo que vale la pena considerar la idea de argumentos de función cero como ideal. Además, tenga en cuenta que incluso su pequeño fragmento de código anterior también tiene funciones de argumento distintas de cero, por lo que creo que depende del contexto.

(Y como otros han señalado, también argumenta que más argumentos lo hacen más difícil desde el punto de vista de la prueba. Pero aquí quería destacar principalmente el ejemplo anterior y su justificación para argumentos de función cero).


-2

Idealmente cero. Uno o dos están bien, tres en ciertos casos.
Cuatro o más suele ser una mala práctica.

Además de los principios de responsabilidad única que otros han notado, también puede pensarlo desde las perspectivas de prueba y depuración.

Si hay un parámetro, conocer sus valores, probarlos y encontrar errores con ellos es relativamente fácil, ya que solo hay un factor. A medida que aumenta los factores, la complejidad total aumenta rápidamente. Para un ejemplo abstracto:

Considere un programa de "qué ponerse en este clima". Considere qué podría hacer con una entrada: temperatura. Como puede imaginar, los resultados de qué ponerse son bastante simples en función de ese único factor. Ahora considere lo que el programa podría / podría / debería hacer si se supera la temperatura, humedad, punto de rocío, precipitación, etc.


12
Si una función tiene cero parámetros, está devolviendo un valor constante (útil en algunas circunstancias, pero más bien limitante) o está utilizando algún estado oculto que sería mejor hacer explícito. (En las llamadas al método OO, el objeto de contexto es lo suficientemente explícito como para no causar problemas.)
Donal Fellows

44
-1 por no citar la fuente
Joshua Drake

¿Estás diciendo en serio que idealmente todas las funciones no tomarían parámetros? ¿O es esta hipérbole?
GreenAsJade

1
Vea el argumento del tío Bob en: informmit.com/articles/article.aspx?p=1375308 y observe que en la parte inferior dice "Las funciones deben tener una pequeña cantidad de argumentos. Ningún argumento es mejor, seguido de uno, dos y tres "Más de tres es muy cuestionable y debe evitarse con prejuicio".
Michael Durrant

He dado la fuente. Divertido no hay comentarios desde entonces. También he tratado de responder a la parte de la "guía", ya que muchos consideran que el tío Bob y Clean Code son pautas. Es interesante que la respuesta superior enormemente votada (actualmente) dice que no conoce ninguna directriz. El tío Bob no tenía la intención de ser autoritario, pero de alguna manera lo es, y esta respuesta al menos trata de ser una respuesta a los detalles de la pregunta.
Michael Durrant
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.