Gemini peut devenir votre copilote pour écrire, refactorer et fiabiliser des scripts Google Apps Script (GAS). L’objectif n’est pas seulement de “générer du code”, mais d’obtenir des scripts plus rapides, plus robustes et plus faciles à maintenir. Voici une méthode concrète, du brief au déploiement, avec des exemples prêts à l’emploi.
Quand utiliser Gemini dans votre flux de travail
Utilisez Gemini à trois moments clés : au démarrage pour transformer un besoin métier en squelette de script, pendant l’itération pour diagnostiquer et corriger, et en fin de cycle pour documenter, tester et accélérer les performances. Cette boucle courte vous évite de “bricoler” à l’aveugle et réduit les aller-retours avec l’éditeur.
Poser un brief clair qui produit du bon code
Un prompt efficace décrit la source des données (Sheets, Drive, Gmail, Calendar), l’action attendue, les contraintes de performance et le format de sortie. Précisez toujours les limites (quota, volumes, latence).
Exemple de brief utile :
“Écris un script Apps Script qui lit 5 000 lignes d’une feuille ‘Leads’ (colonnes A:D), nettoie les emails, enrichit la colonne ‘domaine’ et alimente une feuille ‘Scores’. Le script doit fonctionner en moins de 5 minutes, utiliser getValues() en bloc, ne pas faire de setValue en boucle, et logguer le temps d’exécution.”
Gemini génèrera un premier jet que vous pourrez ensuite optimiser.
Modèle de squelette prêt à améliorer
Commencez avec un modèle minimaliste que Gemini adaptera à votre cas :
function main() {
const started = Date.now();
const ss = SpreadsheetApp.getActive();
const src = ss.getSheetByName('Leads');
const dst = ss.getSheetByName('Scores') || ss.insertSheet('Scores');
const values = src.getDataRange().getValues();
const header = values.shift();
const out = [ ['email','domaine','score'] ];
for (const row of values) {
const email = String(row[0]).trim().toLowerCase();
if (!email || !/@/.test(email)) continue;
const domaine = email.split('@')[1] || '';
const score = scoreFromDomain_(domaine);
out.push([email, domaine, score]);
}
dst.clearContents();
dst.getRange(1,1,out.length,out[0].length).setValues(out);
Logger.log(`Done in ${Date.now() - started} ms`);
}
function scoreFromDomain_(d) {
if (!d) return 0;
if (d.endsWith('.edu')) return 90;
if (d.includes('gmail') || d.includes('yahoo')) return 10;
return 50;
}function main() {
const started = Date.now();
const ss = SpreadsheetApp.getActive();
const src = ss.getSheetByName('Leads');
const dst = ss.getSheetByName('Scores') || ss.insertSheet('Scores');
const values = src.getDataRange().getValues();
const header = values.shift();
const out = [ ['email','domaine','score'] ];
for (const row of values) {
const email = String(row[0]).trim().toLowerCase();
if (!email || !/@/.test(email)) continue;
const domaine = email.split('@')[1] || '';
const score = scoreFromDomain_(domaine);
out.push([email, domaine, score]);
}
dst.clearContents();
dst.getRange(1,1,out.length,out[0].length).setValues(out);
Logger.log(`Done in ${Date.now() - started} ms`);
}
function scoreFromDomain_(d) {
if (!d) return 0;
if (d.endsWith('.edu')) return 90;
if (d.includes('gmail') || d.includes('yahoo')) return 10;
return 50;
}function main() {
const started = Date.now();
const ss = SpreadsheetApp.getActive();
const src = ss.getSheetByName('Leads');
const dst = ss.getSheetByName('Scores') || ss.insertSheet('Scores');
const values = src.getDataRange().getValues();
const header = values.shift();
const out = [ ['email','domaine','score'] ];
for (const row of values) {
const email = String(row[0]).trim().toLowerCase();
if (!email || !/@/.test(email)) continue;
const domaine = email.split('@')[1] || '';
const score = scoreFromDomain_(domaine);
out.push([email, domaine, score]);
}
dst.clearContents();
dst.getRange(1,1,out.length,out[0].length).setValues(out);
Logger.log(`Done in ${Date.now() - started} ms`);
}
function scoreFromDomain_(d) {
if (!d) return 0;
if (d.endsWith('.edu')) return 90;
if (d.includes('gmail') || d.includes('yahoo')) return 10;
return 50;
}Demandez ensuite à Gemini d’optimiser des points précis : gestion d’erreurs, séparation logique, instrumentation, contrôles d’entrées.
Réécrire du code procédural en fonctions pures
La maintenabilité passe par des fonctions pures, testables et sans effets de bord. Collez votre code existant dans Gemini en lui demandant de :
découper en petites fonctions (lecture, transformation, écriture),
retirer les getValue() / setValue() dans les boucles au profit de batch,
isoler la logique métier pour pouvoir la tester sans Google Services.
Prompt type :
“Transforme ce script pour qu’il n’appelle Sheets qu’en lecture/écriture groupée, sépare la logique métier en fonctions pures, et ajoute un validateRow(row) qui retourne une erreur descriptive si les données sont invalides.”
Accélérer les scripts : 7 leviers concrets
Batcher l’I/O : lire et écrire en blocs (getValues() / setValues()), pas dans la boucle.
Limiter les appels aux services : regrouper les accès Drive, Gmail, Calendar.
Éviter les conversions répétées : parsez une fois, passez des références.
Préférer les maps/sets pour les recherches rapides sur des listes.
Mémoriser des informations calculées fréquentes (cache).
Arrêter tôt si un seuil temps est dépassé, et planifier une reprise.
Logger le temps par étape pour repérer les goulots d’étranglement.
Exemple d’instrumentation simple :
function withTimer_(label, fn) {
const t0 = Date.now();
const r = fn();
Logger.log(`${label}: ${Date.now() - t0} ms`);
return r;
}
function main() {
const data = withTimer_('read', () => readLeads_());
const enriched = withTimer_('transform', () => transform_(data));
withTimer_('write', () => writeScores_(enriched));
}function withTimer_(label, fn) {
const t0 = Date.now();
const r = fn();
Logger.log(`${label}: ${Date.now() - t0} ms`);
return r;
}
function main() {
const data = withTimer_('read', () => readLeads_());
const enriched = withTimer_('transform', () => transform_(data));
withTimer_('write', () => writeScores_(enriched));
}function withTimer_(label, fn) {
const t0 = Date.now();
const r = fn();
Logger.log(`${label}: ${Date.now() - t0} ms`);
return r;
}
function main() {
const data = withTimer_('read', () => readLeads_());
const enriched = withTimer_('transform', () => transform_(data));
withTimer_('write', () => writeScores_(enriched));
}Utiliser efficacement le cache
Le CacheService évite de recalculer des valeurs coûteuses (lookup externe, regex complexes). Gemini peut ajouter ce pattern à vos fonctions.
function cached_(key, ttlSec, producer) {
const cache = CacheService.getScriptCache();
const hit = cache.get(key);
if (hit !== null) return JSON.parse(hit);
const val = producer();
cache.put(key, JSON.stringify(val), ttlSec);
return val;
}function cached_(key, ttlSec, producer) {
const cache = CacheService.getScriptCache();
const hit = cache.get(key);
if (hit !== null) return JSON.parse(hit);
const val = producer();
cache.put(key, JSON.stringify(val), ttlSec);
return val;
}function cached_(key, ttlSec, producer) {
const cache = CacheService.getScriptCache();
const hit = cache.get(key);
if (hit !== null) return JSON.parse(hit);
const val = producer();
cache.put(key, JSON.stringify(val), ttlSec);
return val;
}Appel type : cached_('domainRules', 3600, loadRulesFromSheet_).
Gérer les quotas et les limites de temps
GAS impose des limites quotidiennes et un temps d’exécution (~6 minutes typiquement). Gemini peut vous proposer un mécanisme de reprise.
Chunking : traitez par paquets (ex. 1 000 lignes), stockez l’offset dans PropertiesService.
Déclencheur continu : créez un trigger temporaire pour continuer après la fenêtre.
function processBatch() {
const props = PropertiesService.getScriptProperties();
const offset = parseInt(props.getProperty('OFFSET') || '0', 10);
const size = 1000;
const { rows, done, nextOffset } = readChunk_(offset, size);
const out = transform_(rows);
writeChunk_(out, offset);
if (!done) {
props.setProperty('OFFSET', String(nextOffset));
ScriptApp.newTrigger('processBatch').timeBased().after(10 * 1000).create();
} else {
props.deleteAllProperties();
}
}function processBatch() {
const props = PropertiesService.getScriptProperties();
const offset = parseInt(props.getProperty('OFFSET') || '0', 10);
const size = 1000;
const { rows, done, nextOffset } = readChunk_(offset, size);
const out = transform_(rows);
writeChunk_(out, offset);
if (!done) {
props.setProperty('OFFSET', String(nextOffset));
ScriptApp.newTrigger('processBatch').timeBased().after(10 * 1000).create();
} else {
props.deleteAllProperties();
}
}function processBatch() {
const props = PropertiesService.getScriptProperties();
const offset = parseInt(props.getProperty('OFFSET') || '0', 10);
const size = 1000;
const { rows, done, nextOffset } = readChunk_(offset, size);
const out = transform_(rows);
writeChunk_(out, offset);
if (!done) {
props.setProperty('OFFSET', String(nextOffset));
ScriptApp.newTrigger('processBatch').timeBased().after(10 * 1000).create();
} else {
props.deleteAllProperties();
}
}Demandez à Gemini de générer les fonctions readChunk_ et writeChunk_ adaptées à votre feuille.
Debugging assisté : faire expliquer les erreurs
Copiez le message d’erreur et le bloc de code fautif. Demandez à Gemini une explication d’abord, puis une correction raisonnée. Contraignez la réponse : “explique la cause probable”, “propose un patch minimal”, “ne change pas les signatures de fonctions”.
Prompt type :
“Voici l’erreur ‘TypeError: Cannot read properties of null (reading getDataRange)’, voici la fonction concernée. Explique la cause racine la plus probable, propose une correction minimale et un test rapide pour valider.”
Refactoriser vers un style testable
Même si GAS n’a pas de framework de tests natif, vous pouvez isoler la logique pure et la tester via une feuille dédiée ou un petit harnais.
function test_transform_() {
const sample = [
['email','name'],
['alice@exemple.com','Alice'],
['','Bob'],
];
const out = transform_(sample);
Logger.log(JSON.stringify(out, null, 2));
}function test_transform_() {
const sample = [
['email','name'],
['alice@exemple.com','Alice'],
['','Bob'],
];
const out = transform_(sample);
Logger.log(JSON.stringify(out, null, 2));
}function test_transform_() {
const sample = [
['email','name'],
['alice@exemple.com','Alice'],
['','Bob'],
];
const out = transform_(sample);
Logger.log(JSON.stringify(out, null, 2));
}Demandez à Gemini de fournir 5 cas de test représentatifs (emails invalides, doublons, caractères spéciaux, champs manquants).
Patterns utiles pour Sheets, Drive, Gmail
Lecture/écriture Sheets fiable
function readRange_(sheetName) {
const sh = SpreadsheetApp.getActive().getSheetByName(sheetName);
if (!sh) throw new Error(`Sheet ${sheetName} introuvable`);
const values = sh.getDataRange().getValues();
const header = values.shift();
return { header, rows: values };
}
function writeMatrix_(sheetName, matrix) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName(sheetName) || ss.insertSheet(sheetName);
sh.clearContents();
sh.getRange(1,1,matrix.length,matrix[0].length).setValues(matrix);
}function readRange_(sheetName) {
const sh = SpreadsheetApp.getActive().getSheetByName(sheetName);
if (!sh) throw new Error(`Sheet ${sheetName} introuvable`);
const values = sh.getDataRange().getValues();
const header = values.shift();
return { header, rows: values };
}
function writeMatrix_(sheetName, matrix) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName(sheetName) || ss.insertSheet(sheetName);
sh.clearContents();
sh.getRange(1,1,matrix.length,matrix[0].length).setValues(matrix);
}function readRange_(sheetName) {
const sh = SpreadsheetApp.getActive().getSheetByName(sheetName);
if (!sh) throw new Error(`Sheet ${sheetName} introuvable`);
const values = sh.getDataRange().getValues();
const header = values.shift();
return { header, rows: values };
}
function writeMatrix_(sheetName, matrix) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName(sheetName) || ss.insertSheet(sheetName);
sh.clearContents();
sh.getRange(1,1,matrix.length,matrix[0].length).setValues(matrix);
}Recherche Drive rapide
function listPdfsInFolder_(folderId, limit=100) {
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByType(MimeType.PDF);
const res = [];
while (files.hasNext() && res.length < limit) {
const f = files.next();
res.push([f.getName(), f.getId(), f.getSize()]);
}
return res;
}function listPdfsInFolder_(folderId, limit=100) {
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByType(MimeType.PDF);
const res = [];
while (files.hasNext() && res.length < limit) {
const f = files.next();
res.push([f.getName(), f.getId(), f.getSize()]);
}
return res;
}function listPdfsInFolder_(folderId, limit=100) {
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByType(MimeType.PDF);
const res = [];
while (files.hasNext() && res.length < limit) {
const f = files.next();
res.push([f.getName(), f.getId(), f.getSize()]);
}
return res;
}Traitement Gmail par lots
function archiveOldNewsletters_() {
const threads = GmailApp.search('label:newsletter older_than:30d', 0, 200);
GmailApp.moveThreadsToArchive(threads);
Logger.log(`Archivés: ${threads.length}`);
}function archiveOldNewsletters_() {
const threads = GmailApp.search('label:newsletter older_than:30d', 0, 200);
GmailApp.moveThreadsToArchive(threads);
Logger.log(`Archivés: ${threads.length}`);
}function archiveOldNewsletters_() {
const threads = GmailApp.search('label:newsletter older_than:30d', 0, 200);
GmailApp.moveThreadsToArchive(threads);
Logger.log(`Archivés: ${threads.length}`);
}Demandez à Gemini d’ajouter des garde-fous (labels exclus, whitelists de domaines).
Sécurité et permissions : les bons réflexes
Scopes minimaux : n’activez que les services nécessaires.
Vérifications d’entrée : validez les emails, IDs, tailles de fichiers.
Journalisation : logguez sans stocker de données sensibles.
Rôles : si vous publiez un add-on, documentez clairement ce que le script peut faire.
Demandez à Gemini d’auditer votre script : “liste tous les appels à des services externes, les scopes implicites et les lieux où une validation d’entrée manque.”
Documentation et commentaires générés
Faites écrire à Gemini des docstrings JSDoc cohérents et un README concis avec prérequis, limites, et procédures de reprise. La documentation évite le “bus factor”.
Prompt type :
“Ajoute des JSDoc à toutes les fonctions, décris les paramètres, les valeurs de retour et les erreurs possibles. Génère ensuite un README avec prérequis, installation, exécution, limites et check-list de dépannage.”
Intégration continue locale avec Clasp
Si vous sortez du simple “script lié à un fichier”, basculez sur Clasp pour versionner votre code. Demandez à Gemini un guide court : initialisation, push/pull, gestion des versions, et organisation en modules.
Exemple complet : nettoyer des emails et enrichir un score
Voici un mini-flux assemblé, prêt à adapter :
function run() {
const { header, rows } = readRange_('Leads');
const cleaned = transform_( [header, ...rows] );
writeMatrix_('Scores', cleaned);
}
function transform_(matrix) {
const [header, ...rows] = matrix;
const out = [['email','domaine','score']];
for (const r of rows) {
const email = String(r[0] || '').trim().toLowerCase();
if (!validEmail_(email)) continue;
const domaine = email.split('@')[1];
const score = cached_(`score:${domaine}`, 3600, () => scoreFromDomain_(domaine));
out.push([email, domaine, score]);
}
return out;
}
function validEmail_(s) { return /.+@.+\..+/.test(s); }
function scoreFromDomain_(d) {
if (!d) return 0;
if (d.endsWith('.edu')) return 90;
if (/(gmail|yahoo|outlook)\./.test(d)) return 10;
return 50;
}
function run() {
const { header, rows } = readRange_('Leads');
const cleaned = transform_( [header, ...rows] );
writeMatrix_('Scores', cleaned);
}
function transform_(matrix) {
const [header, ...rows] = matrix;
const out = [['email','domaine','score']];
for (const r of rows) {
const email = String(r[0] || '').trim().toLowerCase();
if (!validEmail_(email)) continue;
const domaine = email.split('@')[1];
const score = cached_(`score:${domaine}`, 3600, () => scoreFromDomain_(domaine));
out.push([email, domaine, score]);
}
return out;
}
function validEmail_(s) { return /.+@.+\..+/.test(s); }
function scoreFromDomain_(d) {
if (!d) return 0;
if (d.endsWith('.edu')) return 90;
if (/(gmail|yahoo|outlook)\./.test(d)) return 10;
return 50;
}
function run() {
const { header, rows } = readRange_('Leads');
const cleaned = transform_( [header, ...rows] );
writeMatrix_('Scores', cleaned);
}
function transform_(matrix) {
const [header, ...rows] = matrix;
const out = [['email','domaine','score']];
for (const r of rows) {
const email = String(r[0] || '').trim().toLowerCase();
if (!validEmail_(email)) continue;
const domaine = email.split('@')[1];
const score = cached_(`score:${domaine}`, 3600, () => scoreFromDomain_(domaine));
out.push([email, domaine, score]);
}
return out;
}
function validEmail_(s) { return /.+@.+\..+/.test(s); }
function scoreFromDomain_(d) {
if (!d) return 0;
if (d.endsWith('.edu')) return 90;
if (/(gmail|yahoo|outlook)\./.test(d)) return 10;
return 50;
}
Demandez à Gemini d’ajouter des tests, des statistiques (nb de lignes ignorées, taux d’emails invalides), et une alerte si la proportion d’erreurs dépasse un seuil.
Méthode d’amélioration continue
Adoptez un rituel hebdomadaire : mesurez le temps d’exécution moyen, les erreurs, le volume traité, et le taux de reprises. Demandez à Gemini des pistes ciblées : “réduis la complexité cyclomatique”, “remplace cette boucle par une opération vectorisée sur le tableau”, “élimine les conversions inutiles”.
Prompts utiles à copier-coller
“Optimise ce script pour qu’il n’ait plus aucun appel aux services Google dans des boucles.”
“Explique en 5 points pourquoi ce code est lent et propose un patch minimal.”
“Ajoute des JSDoc, des validations d’entrées et un mécanisme de cache pour la fonction lookupDomain.”
“Refactorise en fonctions pures testables et écris un harnais de test test_*().”
“Ajoute un traitement par lots de 1 000 lignes avec persistance de l’offset via PropertiesService.”