Arnaud Labourel
17 ou 19 septembre 2018
Une classe (d’objet) définit des :
public NameOfTheClass{
public final Type1 attribute1;
private Type2 attribute2;
public NameOfTheClass(...){
// constructor code
}
public getAttribute2(){
return this.attribute2;
}
}
public
/private
: accessibilité en dehors de la classe
final
: changement de valeur impossible après construction
null
soit la référence d’une instance compatible avec le type de la variable.Counter counter1 = new Counter(12);
Counter counter2 = new Counter(24);
Counter counter3 = counter1;
Counter counter4 = null;
System.out.println(counter1.position); // 12
System.out.println(counter2.position); // 24
System.out.println(counter3.position); // 12
System.out.println(counter4.position);
// NullPointerException
Un programme propre :
Un programme informatique est de bonne qualité s’il est facile pour un développeur externe de rajouter un nouvelle fonctionnalité.
Un développeur professionnel se force à écrire du code compréhensible par le plus grand nombre.
“Mal nommer un objet, c’est ajouter au malheur de ce monde”
La L3 info : MIAGE ne dépend pas de la même UFR que la L2 info : MI, elle dépend de la FEG et non de la FS. Pour faire vos IA et IP, vous devez donc contacter la scol de Forbin et non celle de SCH.
Les noms donnés aux variables/méthodes/attributs/classes sont essentiels pour la lisibilité du code.
Un code bien écrit passe d’abord par des noms bien choisis.
Êtes-vous capable de dire rapidement ce que fait cette classe ?
Le même programme avec un meilleur nommage des variables :
class Counter {
private int count = 0;
public void increment(){ count++; }
public int value(){ return count; }
}
Des noms donnés au hasard et en dépit des conventions rendent le code illisible en cachant l’intention du code.
Autre exemple de programme mal écrit :
Programme légèrement refactoré :
List<Rectangle> findSquares(List<Rectangle> rectangles){
List<Rectangle> squares = new ArrayList<Rectangle>();
for(Rectangle rectangle : rectangles)
if (rectangle.isSquare()) squares.add(rectangle);
return squares;
}
class Rectangle{
public int width, height;
/* ... */
boolean isSquare(){ return width == height; }
}
i
, j
, …a1
, a2
, a3
, …rec
, res
, …temporary
, result
, …accountList
doit être une List
(et pas un array
ou un autre type)persons
et non person
.genymdhms
, …Le Java et la quasi-totalité des langages de programmation utilise l’anglais (dans les mot-clés et les librairies standards).
\(\Rightarrow\) On doit programmer en anglais pour avoir la cohérence du code
Utiliser l’anglais permet aussi d’augmenter le nombre de personnes pouvant lire le code et d’avoir de nombreux exemples existants pour s’inspirer.
La plupart des langages (Java inclus) ont des conventions pour l’écriture des noms et la manière de coder les espaces.
Utilisation du Camel Case (casse de chameau) dans la plupart des cas.
UpperCamelCase (commençant par une majuscule) : pour les noms de classes
Exemples : Collection
, ArrayList
, LinkedBlockingQueue
, BeanContextServicesSupport
, …
lowerCamelCase (commençant par une minuscule) : pour les méthodes, variables, attributs
Exemples : add
, length
, addAll
, modCount
, lastIndexOf
, …
ALL_CAPS : pour les constantes
Exemples : PI
, HALF_UP
, DAYS_IN_WEEK
, …
Méthodes modifiant l’état de l’objet
\(\Rightarrow\) groupe verbal à l’infinitif.
boolean add(E element)
E set(int index, E element)
boolean removeAll(Collection<?> c)
Méthodes renvoyant une partie de l’état de l’objet \(\Rightarrow\) groupe nominal ou getter.
int size()
List<E> subList(int fromIndex, int toIndex)
int hashcode()
ListIterator<E> listIterator()
E get(int index)
Color getBackground()
float getOpacity()
Méthodes testant un prédicat sur l’objet \(\Rightarrow\) groupe verbal au présent.
boolean isEmpty()
boolean contains(Object o)
boolean equals(Object o)
Exemples :
String toString()
Object[] toArray()
Les règles ne sont pas absolues mais juste des conventions qui peuvent avoir des exceptions.
En général le plus simple est de s’inspirer de l’existant : par exemple la JDK et de bien réfléchir lorsqu’on souhaite déroger aux règles.
L’animal vertébré vivipare caractérisé par la présence de mamelles, qui se nourrit en grignotant à l’aide de leurs deux paires d’incisives, et qui se multiplie avec une rapidité d’intensité forte, aux organes d’audition et d’équilibration à l’étendue supérieure à la moyenne dans le sens de la longueur, fait descendre par le gosier, postérieurement à une réduction en petites parcelles avec les dents, un être vivant appartenant au règne végétal cultivé pour l’usage culinaire, muni de pédicelles partant en faisceau depuis son pédoncule, et dont la partie souterraine permettant la fixation au sol est d’une couleur située à l’extrémité du spectre rappelant celle du sang.
Le mammifère lagomorphe très prolifique aux longues oreilles avale après mâchage une plante potagère ombellifère à racine rouge.
Le lapin mange une carotte.
class User {
private boolean authenticated;
private String password;
public boolean checkPassword(String password) {
if (password.equals(this.password)) {
authenticated = true;
return true;
}
return false;
}
}
La méthode authentifie l’utilisateur alors qu’elle ne devrait que vérifier la validité du mot de passe d’après son nom.
Il existe de nombreux principes de programmation permettant de guider l’écriture de code :
Les 5 principes SOLID (principes de conception objet) que vous verrez en détail en L3 dans le cours de Programmation et conception
Une variable/méthode/classe doit n’avoir qu’une seule signification.
Une variable/méthode/classe ne doit pas avoir :
Le principe Do One thing est fortement lié aux principes suivants :
Une méthode ne doit avoir qu’une responsabilité = raison de changer.
doOneThingAndAnotherThing
if
dans un for
dans un for
int countAndPrintSquares(List<Rectangle> rectangles) {
int countSquare = 0;
for (Rectangle rectangle : rectangles) {
if (rectangle.width == rectangle.height) {
System.out.println(rectangle);
countSquare++;
}
}
return countSquare;
}
La méthode doit :
boolean isSquare(){ return this.width == this.height; }
static int printSquares(List<Rectangle> rectangles) {
for (Rectangle rectangle : rectangles) {
if (rectangle.isSquare()) {
System.out.println(rectangle);
}
}
}
static void countSquares(List<Rectangle> rectangles) {
for (Rectangle rectangle : rectangles) {
if (rectangle.isSquare()) squareCount++;
}
return squareCount;
}
Un des 5 principes SOLID : Do One Thing pour les classes
Une classe ne doit avoir qu’une responsabilité = raison de changer
public interface Modem
{
public void dial(string phoneNumber);
public void hangUp();
public void send(char c);
public char receive();
}
Deux responsabilités :
dial
et hangUp
.send
et receive
.Dans un système, toute connaissance doit avoir une représentation unique, non-ambiguë, faisant autorité.
Éviter la redondance de code.
La complexité rend le code illisible.
On peut regrouper les deux arguments x
et y
en seul concept : un Point
.
Code avec drapeau = mauvais
Il est préférable d’écrire deux méthodes :
boolean addToList(String[] source, int index,
List<String> destination) {
if (index < 0 || index >= source.length)
return false;
destination.add(source[index]);
return true;
}
public void copy(String[] source,
List<String> destination) {
int index = 0;
while (addToList(source, index, destination))
index++;
}
Il faut essayer de ne pas mélanger condition de boucle et action.
L’action de la boucle :
void addToList(String[] source, int index,
List<String> destination) {
destination.add(source[index]);
}
Le test de la boucle :
public void copy(String[] source,
List<String> destination) {
for (int index = 0;
indexIsValid(source, index);
index++)
addToList(source, index, destination);
}
Évidemment dans ce cas, on peut écrire directement :
La variable found
ne fait pas partie de la condition de boucle donc :
boolean contains(int[] array, int element) {
for (int i = 0; i < array.length; i++)
if (array[i] == element) return true;
return false;
}
void copyToStop(Node head,
List<String> destination) {
for (Node node=head; node!=null; node=node.next) {
String element = node.element;
if (element.equals("stop"))
break; // or return
destination.add(element);
}
}
Si elle est bien écrite, une méthode courte avec plusieurs points de sortie est plus facile à lire qu’une méthode qui utilise des variables booléennes “artificielles” pour sortir des boucles.
Une fois que votre code fonctionne, vous devez :
Un programme est un document (au même titre qu’une lettre ou qu’un courriel)
\(\Rightarrow\) relire et soigner le fond et la forme.
String get(String[] source, int index) {
// Teste si l'index est dans les limites du tableau.
if (index < 0 || index >= source.length)
return null;
return source[index];
}
Si un commentaire semble nécessaire, le remplacer par une méthode :
Les commentaires se désynchronisent souvent du code :
risque de devenir un jour :
Commentaires inutiles = répétition
Des commentaires qui peuvent sembler utiles :
/* une méthode qui retourne que les carrés : */
List<Rectangle> get(List<Rectangle> list) {
/* le résultat sera stocké dans cette liste : */
List<Rectangle> list2 = new ArrayList<Rectangle>();
for (Rectangle x : list)
if (x.w == x.h /* un carré ? */)
list2.add(x);
return list2;
}
class Rectangle {
public int w; /* largeur */
public int h; /* hauteur */
}
On peut se passer de commentaire en rajoutant une méthode et en nommant correctement les éléments du code.
List<Rectangle> findSquares(List<Rectangle> rectangles) {
List<Rectangle> squares = new ArrayList<Rectangle>();
for (Rectangle rectangle : rectangles)
if (rectangle.isSquare())
squares.add(rectangle);
return squares;
}
class Rectangle {
private int width, height;
boolean isSquare() {
return width == height;
}
}
Documentation ou spécification du comportement d’une méthode :
/**
* Returns <tt>true</tt> if this list contains the
* specified element. More formally, returns
* <tt>true</tt> if and only if this list contains
* at least one element <tt>e</tt> such that
* <tt>(o==null ? e==null : o.equals(e))</tt>.
*
* @param o element whose presence in this list is
* to be tested @return <tt>true</tt> if this list
* contains the specified element
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
JavaDoc permet de génèrer automatiquement une documentation du code à partir de commentaires associés aux classes, méthodes, propriétés, …
La documentation contient :
Un bloc de commentaire Java commençant par /**
deviendra un bloc de commentaire Javadoc qui sera inclus dans la documentation du code source.
Tag | Description |
---|---|
@author | pour preéciser l’auteur de la fonctionnalité |
@deprecated | indique que l’attribut, la méthode ou la classe |
est dépréciée | |
@return | pouyr décrire la valeur de retour |
{@code literal } |
Formate literal en code |
{@link reference } |
permet de créer un lien |
Tools \(\rightarrow\) generate Javadoc
/**
* The {@code Byte} class wraps a value of primitive
* type {@code byte} in an object. An object of type
* {@code Byte} contains a single field whose type is
* {@code byte}.
*
* <p>In addition, this class provides several methods
* for converting a {@code byte} to a {@code String} and
* a {@code String} to a {@code byte}, as well as other
* constants and methods useful when dealing with a
* {@code byte}.
*
* @author Nakul Saraiya
* @author Joseph D. Darcy
*/
Écrire du code lisible
Comment rendre le nommage des méthodes facile ?
En écrivant des méthodes courtes
De préférence une dizaine de ligne maximum.
Comment écrire des méthodes courtes
En extrayant le plus possible les partie du code d’une méthode à d’autres méthodes.
Conseils